Native Serialization
Rust native serialization is the Rust-only wire mode selected with .xlang(false). Use it when
every writer and reader is Rust and the payload should preserve Rust object-graph behavior instead
of the portable xlang type system.
Use Xlang Serialization, the default Rust mode, when bytes must be read by Java, Python, C++, Go, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another non-Rust Fory implementation.
When To Use Native Serialization
Use native serialization when:
- A payload is produced and consumed only by Rust applications.
- The data model uses Rust-specific object graph features such as
Rc<T>,Arc<T>, weak pointers,RefCell<T>,Mutex<T>, trait objects, ordyn Any. - You want faster serialization and smaller size, and every reader uses the same schema as the writer.
- You need compatible schema evolution for Rust-only rolling deployments.
- You want compile-time serializers from
#[derive(ForyStruct)]without portable xlang mapping constraints.
Create a Native-Mode Fory Instance
use fory::{Error, Fory, ForyStruct};
#[derive(ForyStruct, Debug, PartialEq)]
struct Order {
id: i64,
amount: f64,
}
fn main() -> Result<(), Error> {
let mut fory = Fory::builder().xlang(false).build();
fory.register::<Order>(100)?;
let order = Order { id: 1, amount: 42.5 };
let bytes = fory.serialize(&order)?;
let decoded: Order = fory.deserialize(&bytes)?;
assert_eq!(order, decoded);
Ok(())
}
Perform registrations before sharing a Fory instance across threads. Once configured, Fory can
be shared through Arc.
Schema Evolution
Native serialization defaults to compatible mode. Keep that default when Rust-only writer and reader versions can differ:
let mut writer = Fory::builder().xlang(false).build();
let mut reader = Fory::builder().xlang(false).build();
Compatible mode uses metadata to tolerate added, removed, or reordered fields when field identity remains compatible. See Schema Evolution.
For faster serialization and smaller size, set .compatible(false) only when
every reader and writer always uses the same Rust schema.
Registration
Register application structs and enum-like types before serialization:
fory.register::<Order>(100)?;
fory.register_by_name::<Order>("example.Order")?;
Use explicit numeric IDs for compact payloads and stable deployments. Use named registration
when independent teams coordinate type identity by names; add a namespace prefix with . when needed.
Rust Object Surface
Native serialization owns the Rust-specific object surface:
- Structs and tuple structs with
#[derive(ForyStruct)]. - Enums and union-like models supported by Fory derive macros.
Vec, maps, sets, tuples, arrays, and optional values.Box<T>,Rc<T>,Arc<T>,RcWeak<T>, andArcWeak<T>.RefCell<T>andMutex<T>.- Trait objects such as
Box<dyn Trait>,Rc<dyn Trait>, andArc<dyn Trait>. - Runtime type dispatch with
Box<dyn Any>,Rc<dyn Any>, andArc<dyn Any + Send + Sync>for registered non-container payloads. Wrap containers in registered structs, enums, or unions before using them behind erasedAnycarriers. - Date and time carriers, including optional
chronosupport.
Use Basic Serialization, References, and Trait Object Serialization for focused examples.
Shared And Circular References
Native mode can preserve shared references with Rc<T> and Arc<T>:
use fory::{Error, Fory};
use std::rc::Rc;
fn main() -> Result<(), Error> {
let fory = Fory::builder().xlang(false).build();
let shared = Rc::new(String::from("shared"));
let values = vec![shared.clone(), shared.clone()];
let bytes = fory.serialize(&values)?;
let decoded: Vec<Rc<String>> = fory.deserialize(&bytes)?;
assert!(Rc::ptr_eq(&decoded[0], &decoded[1]));
Ok(())
}
Use .track_ref(true) when weak pointers or explicit cyclic graphs need reference tracking:
let mut fory = Fory::builder().xlang(false).track_ref(true).build();
Weak pointers serialize as references to their target when the target is still alive, and as null when the target has been dropped.
Trait Objects
Trait objects are Rust language features and belong in native serialization:
use fory::{register_trait_type, Error, Fory, ForyStruct, Serializer};
trait Animal: Serializer {
fn name(&self) -> &str;
}
#[derive(ForyStruct)]
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
}
register_trait_type!(Animal, Dog);
fn main() -> Result<(), Error> {
let mut fory = Fory::builder().xlang(false).build();
fory.register::<Dog>(100)?;
let value: Box<dyn Animal> = Box::new(Dog { name: "Milo".into() });
let bytes = fory.serialize(&value)?;
let decoded: Box<dyn Animal> = fory.deserialize(&bytes)?;
assert_eq!(decoded.name(), "Milo");
Ok(())
}
Register every concrete implementation that can appear behind the trait object.
Performance Guidelines
- Reuse a configured
Foryinstance and register types before concurrent use. - Use
.compatible(false)only when every reader and writer always uses the same Rust schema and the application wants faster serialization and smaller size. - Use derive-generated serializers for application structs.
- Use
.track_ref(true)only for weak-pointer or cyclic graph scenarios that require it. - Prefer concrete typed fields over
dyn Anyor trait objects on hot paths.
Native And Xlang Comparison
| Requirement | Use native serialization | Use xlang serialization |
|---|---|---|
| Rust-only payloads | Yes | Optional |
| Non-Rust readers or writers | No | Yes |
Rc, Arc, weak pointers | Yes | No |
Trait objects and dyn Any | Yes | No |
| Same-schema compact payloads | Yes | No |
| Compatible schema evolution by default | Yes | Yes |
| Portable type mapping across languages | No | Yes |
Troubleshooting
A non-Rust implementation cannot read the payload
The writer is using native serialization. Rebuild it with .xlang(true) and align type
registration with every peer.
A weak pointer fails to resolve
Use .track_ref(true) and make sure the target object is still alive when serialized. Dropped weak
targets deserialize as null.
A trait object cannot deserialize
Register the trait mapping and every concrete implementation that can appear behind the trait object.
A rolling deployment fails after a field change
Native serialization defaults to compatible mode. Keep that default when schemas can differ.
Related Topics
- Xlang Serialization - Cross-language Rust payloads
- Configuration - Builder options
- Basic Serialization - Object graph serialization
- Shared & Circular References -
Rc,Arc, and weak pointers - Trait Object Serialization - Trait objects and dynamic dispatch
- Schema Evolution - Compatible mode