title: Field Configuration sidebar_position: 5 id: field_configuration license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
This page explains how to configure field-level metadata for serialization in Rust.
Apache Fory™ provides the #[fory(...)] attribute macro to specify optional field-level metadata at compile time. This enables:
The #[fory(...)] attribute is placed on individual struct fields:
use fory::ForyObject; #[derive(ForyObject)] struct Person { #[fory(id = 0)] name: String, #[fory(id = 1)] age: i32, #[fory(id = 2, nullable)] nickname: Option<String>, }
Multiple options are separated by commas.
id = N)Assigns a numeric ID to a field to minimize struct field meta size overhead:
#[derive(ForyObject)] struct User { #[fory(id = 0)] id: i64, #[fory(id = 1)] name: String, #[fory(id = 2)] age: i32, }
Benefits:
Recommendation: It is recommended to configure field IDs for compatible mode since it reduces serialization cost.
Notes:
skip)Excludes a field from serialization:
#[derive(ForyObject)] struct User { #[fory(id = 0)] id: i64, #[fory(id = 1)] name: String, #[fory(skip)] password: String, // Not serialized }
The password field will not be included in serialized output and will remain at its default value after deserialization.
nullable)Controls whether null flags are written for fields:
use fory::{Fory, RcWeak}; #[derive(ForyObject)] struct Record { // RcWeak is nullable by default, override to non-nullable #[fory(id = 0, nullable = false)] required_ref: RcWeak<Data>, }
Default Behavior:
| Type | Default Nullable |
|---|---|
Option<T> | true |
RcWeak<T>, ArcWeak<T> | true |
| All other types | false |
Notes:
Option<T>, RcWeak<T>, ArcWeak<T>, nullable defaults to truenullable = false to override defaults for types that are nullable by defaultref)Controls per-field reference tracking for shared ownership types:
use std::rc::Rc; use std::sync::Arc; #[derive(ForyObject)] struct Container { // Enable reference tracking (default for Rc/Arc) #[fory(id = 0, ref = true)] shared_data: Rc<Data>, // Disable reference tracking #[fory(id = 1, ref = false)] unique_data: Rc<Data>, }
Default Behavior:
| Type | Default Ref Tracking |
|---|---|
Rc<T>, Arc<T> | true |
RcWeak<T>, ArcWeak<T> | true |
Option<Rc<T>>, Option<Arc<T>> | true (inherited) |
| All other types | false |
Use Cases:
encoding)Controls how integer fields are encoded:
#[derive(ForyObject)] struct Metrics { // Variable-length encoding (smaller for small values) #[fory(id = 0, encoding = "varint")] count: i64, // Fixed-length encoding (consistent size) #[fory(id = 1, encoding = "fixed")] timestamp: i64, // Tagged encoding (includes type tag, u64 only) #[fory(id = 2, encoding = "tagged")] value: u64, }
Supported Encodings:
| Type | Options | Default |
|---|---|---|
i32, u32 | varint, fixed | varint |
i64, u64 | varint, fixed, tagged | varint |
When to Use:
varint: Best for values that are often small (default)fixed: Best for values that use full range (e.g., timestamps, hashes)tagged: When type information needs to be preserved (u64 only)compress)A convenience shorthand for controlling integer encoding:
#[derive(ForyObject)] struct Data { // compress = true -> varint encoding (default) #[fory(id = 0, compress)] small_value: i32, // compress = false -> fixed encoding #[fory(id = 1, compress = false)] fixed_value: u32, }
Notes:
compress or compress = true is equivalent to encoding = "varint"compress = false is equivalent to encoding = "fixed"compress and encoding are specified, they must not conflictFory classifies field types to determine default behavior:
| Type Class | Examples | Default Nullable | Default Ref |
|---|---|---|---|
| Primitive | i8, i32, f64, bool | false | false |
| Option | Option<T> | true | false |
| Rc | Rc<T> | false | true |
| Arc | Arc<T> | false | true |
| RcWeak | RcWeak<T> (fory type) | true | true |
| ArcWeak | ArcWeak<T> (fory type) | true | true |
| Other | String, Vec<T>, user types | false | false |
Special Case: Option<Rc<T>> and Option<Arc<T>> inherit the inner type's ref tracking behavior.
use fory::ForyObject; use std::rc::Rc; #[derive(ForyObject, Default)] struct Document { // Required fields with tag IDs #[fory(id = 0)] title: String, #[fory(id = 1)] version: i32, // Optional field (nullable by default for Option) #[fory(id = 2)] description: Option<String>, // Reference-tracked shared pointer #[fory(id = 3)] parent: Rc<Document>, // Nullable + reference-tracked #[fory(id = 4, nullable)] related: Option<Rc<Document>>, // Counter with varint encoding (small values) #[fory(id = 5, encoding = "varint")] view_count: u64, // Timestamp with fixed encoding (full range values) #[fory(id = 6, encoding = "fixed")] created_at: i64, // Skip sensitive field #[fory(skip)] internal_state: String, } fn main() { let fory = fory::Fory::default(); let doc = Document { title: "My Document".to_string(), version: 1, description: Some("A sample document".to_string()), parent: Rc::new(Document::default()), related: None, // Allowed because nullable view_count: 42, created_at: 1704067200, internal_state: "secret".to_string(), // Will be skipped }; let bytes = fory.serialize(&doc); let decoded: Document = fory.deserialize(&bytes).unwrap(); }
Invalid configurations are caught at compile time:
// Error: duplicate field IDs #[derive(ForyObject)] struct Bad { #[fory(id = 0)] field1: String, #[fory(id = 0)] // Compile error: duplicate id field2: String, } // Error: invalid id value #[derive(ForyObject)] struct Bad2 { #[fory(id = -2)] // Compile error: id must be >= -1 field: String, } // Error: conflicting encoding attributes #[derive(ForyObject)] struct Bad3 { #[fory(compress = true, encoding = "fixed")] // Compile error: conflict field: i32, }
When serializing data to be read by other languages (Java, C++, Go, Python), use field configuration to match encoding expectations:
#[derive(ForyObject)] struct CrossLangData { // Matches Java Integer with varint #[fory(id = 0, encoding = "varint")] int_var: i32, // Matches Java Integer with fixed #[fory(id = 1, encoding = "fixed")] int_fixed: i32, // Matches Java Long with tagged encoding #[fory(id = 2, encoding = "tagged")] long_tagged: u64, // Nullable pointer matches Java nullable reference #[fory(id = 3, nullable)] optional: Option<String>, }
Compatible mode supports schema evolution. It is recommended to configure field IDs to reduce serialization cost:
// Version 1 #[derive(ForyObject)] struct DataV1 { #[fory(id = 0)] id: i64, #[fory(id = 1)] name: String, } // Version 2: Added new field #[derive(ForyObject)] struct DataV2 { #[fory(id = 0)] id: i64, #[fory(id = 1)] name: String, #[fory(id = 2)] email: Option<String>, // New nullable field }
Data serialized with V1 can be deserialized with V2 (new field will be None).
Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead):
#[derive(ForyObject)] struct Data { id: i64, name: String, }
Option<T>, RcWeak<T>, and ArcWeak<T> are nullable by default; all other types are non-nullableRc<T>, Arc<T>, RcWeak<T>, and ArcWeak<T> enable ref tracking by default; all other types are disabledYou need to configure fields when:
Option<T>)ref = true)// Xlang mode: explicit configuration required #[derive(ForyObject)] struct User { #[fory(id = 0)] name: String, // Non-nullable by default #[fory(id = 1)] email: Option<String>, // Nullable (Option<T>) #[fory(id = 2, ref = true)] friend: Rc<User>, // Ref tracking (default for Rc) }
| Type | Default Nullable | Default Ref Tracking |
|---|---|---|
Primitives, String | false | false |
Option<T> | true | false |
Rc<T>, Arc<T> | false | true |
RcWeak<T>, ArcWeak<T> | true | true |
skip for sensitive data: Passwords, tokens, internal statevarint for small values, fixed for full-range values| Option | Syntax | Description | Valid For |
|---|---|---|---|
id | id = N | Field tag ID to reduce metadata size | All fields |
skip | skip | Exclude field from serialization | All fields |
nullable | nullable or nullable = bool | Control null flag writing | All fields |
ref | ref or ref = bool | Control reference tracking | Rc, Arc, weak types |
encoding | encoding = "varint/fixed/tagged" | Integer encoding method | i32, u32, i64, u64 |
compress | compress or compress = bool | Shorthand for varint/fixed | i32, u32 |