Skip to main content
Version: 1.0.0

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, or another non-Rust Fory runtime.

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, or dyn Any.
  • You want schema-consistent Rust payloads for lockstep services.
  • 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 Runtime

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 schema-consistent mode. Enable compatible mode only when Rust-only writer and reader versions can differ:

let mut writer = Fory::builder().xlang(false).compatible(true).build();
let mut reader = Fory::builder().xlang(false).compatible(true).build();

Compatible mode uses schema metadata to tolerate added, removed, or reordered fields when field identity remains compatible. See Schema Evolution.

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 namespace/type-name registration when independent teams coordinate type identity by names.

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>, and ArcWeak<T>.
  • RefCell<T> and Mutex<T>.
  • Trait objects such as Box<dyn Trait>, Rc<dyn Trait>, and Arc<dyn Trait>.
  • Runtime type dispatch with Rc<dyn Any> and Arc<dyn Any>.
  • Date and time carriers, including optional chrono support.

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 runtime 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).compatible(true).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 Fory instance and register types before concurrent use.
  • Keep native schema-consistent mode for lockstep Rust services.
  • Enable .compatible(true) only when Rust-only schema evolution is required.
  • 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 Any or trait objects on hot paths.

Native And Xlang Comparison

RequirementUse native serializationUse xlang serialization
Rust-only payloadsYesOptional
Non-Rust readers or writersNoYes
Rc, Arc, weak pointersYesNo
Trait objects and dyn AnyYesNo
Schema-consistent same-language payloadsYesNo
Compatible schema evolution by defaultNoYes
Portable type mapping across runtimesNoYes

Troubleshooting

A non-Rust runtime cannot read the payload

The writer is using native serialization. Rebuild it with .xlang(true) and align type registration with every peer runtime.

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 schema-consistent mode. Use .compatible(true) on both writer and reader when schemas can differ.