Skip to main content
Version: 1.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.