Skip to main content
Version: dev

Basic Serialization

This page covers basic object graph serialization and supported types in the default xlang mode for Fory Rust.

Object Graph Serialization

Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The #[derive(ForyStruct)] macro generates efficient serialization code at compile time, eliminating reflection overhead.

Key capabilities:

  • Nested struct serialization with arbitrary depth
  • Collection types (Vec, HashMap, HashSet, BTreeMap)
  • Optional fields with Option<T>
  • Automatic handling of primitive types and strings
  • Efficient binary encoding with variable-length integers
use fory::{Fory, Error};
use fory::ForyStruct;
use std::collections::HashMap;

#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
address: Address,
hobbies: Vec<String>,
metadata: HashMap<String, String>,
}

#[derive(ForyStruct, Debug, PartialEq)]
struct Address {
street: String,
city: String,
country: String,
}

let mut fory = Fory::builder().xlang(true).build();
fory.register_by_name::<Address>("example.Address").unwrap();
fory.register_by_name::<Person>("example.Person").unwrap();

let person = Person {
name: "John Doe".to_string(),
age: 30,
address: Address {
street: "123 Main St".to_string(),
city: "New York".to_string(),
country: "USA".to_string(),
},
hobbies: vec!["reading".to_string(), "coding".to_string()],
metadata: HashMap::from([
("role".to_string(), "developer".to_string()),
]),
};

let bytes = fory.serialize(&person).unwrap();
let decoded: Person = fory.deserialize(&bytes)?;
assert_eq!(person, decoded);

Supported Types

Primitive Types

Rust TypeDescription
boolBoolean
i8, i16, i32, i64Signed integers
f32, f64Floating point
BFloat1616-bit brain floating point
StringUTF-8 string

Collections

Rust TypeDescription
Vec<T>Dynamic array
VecDeque<T>Double-ended queue
LinkedList<T>Doubly-linked list
HashMap<K, V>Hash map
BTreeMap<K, V>Ordered map
HashSet<T>Hash set
BTreeSet<T>Ordered set
BinaryHeap<T>Binary heap
Option<T>Optional value

Vec<BFloat16> is the dense carrier when the schema is array<bfloat16>.

Smart Pointers

Rust TypeDescription
Box<T>Heap allocation
Rc<T>Reference counting (shared refs tracked)
Arc<T>Thread-safe reference counting (shared refs tracked)
RcWeak<T>Weak reference to Rc<T> (breaks circular refs)
ArcWeak<T>Weak reference to Arc<T> (breaks circular refs)
RefCell<T>Interior mutability (runtime borrow checking)
Mutex<T>Thread-safe interior mutability

Date and Time

Rust TypeDescription
DateDate without timezone, stored as epoch days
TimestampPoint in time, stored as epoch seconds and nanos
DurationSigned duration, stored as seconds and normalized nanos

The built-in carriers expose dependency-free constructors, accessors, conversions, and checked arithmetic:

use fory::{Date, Duration, Timestamp};

let date = Date::from_epoch_days(19_782);
assert_eq!(date.checked_add_days(1)?.epoch_days(), 19_783);

let timestamp = Timestamp::from_epoch_millis(-1);
assert_eq!(timestamp.to_epoch_millis()?, -1);

let duration = Duration::from_parts(1, 1_500_000_000)?;
assert_eq!(duration.to_millis()?, 2_500);
let later = timestamp.checked_add_duration(duration)?;

chrono::NaiveDate, chrono::NaiveDateTime, and chrono::Duration are supported when the Rust chrono feature is enabled:

[dependencies]
fory = { version = "1.5.0", features = ["chrono"] }

Custom Types

Use #[derive(ForyStruct)] for object graph serialization. The separate Rust Row Format guide documents #[derive(ForyRow)] and its supported type set.

Serialization APIs

use fory::{Fory, Reader};

let mut fory = Fory::builder().xlang(true).build();
fory.register::<MyStruct>(1)?;

let obj = MyStruct { /* ... */ };

// Basic serialize/deserialize
let bytes = fory.serialize(&obj)?;
let decoded: MyStruct = fory.deserialize(&bytes)?;

// Serialize to existing buffer
let mut buf: Vec<u8> = vec![];
fory.serialize_to(&mut buf, &obj)?;

// Deserialize from reader
let mut reader = Reader::new(&buf);
let decoded: MyStruct = fory.deserialize_from(&mut reader)?;

When the Rust value type uses an external structural serializer or custom serializer, select it explicitly at the root:

let bytes = fory.serialize_with::<UserSerializer>(&user)?;
let decoded: third_party::User =
fory.deserialize_with::<UserSerializer>(&bytes)?;

Carrier serializers compose the same selection for a root container:

use fory::VecSerializer;

let bytes =
fory.serialize_with::<VecSerializer<UserSerializer>>(&users)?;
let decoded: Vec<third_party::User> =
fory.deserialize_with::<VecSerializer<UserSerializer>>(&bytes)?;

See External-Type Serialization for field annotations, all supported carriers, and registration.

Performance Tips

  • Buffer Pre-allocation: Minimizes memory allocations during serialization
  • Compact Encoding: Variable-length encoding for space efficiency
  • Little-Endian: Optimized for modern CPU architectures
  • Reference Deduplication: Shared objects serialized only once

Cross-Language Interoperability

The default xlang format is shared by all supported Fory implementations. The following sections cover its cross-language type mapping, type identity, and interoperability requirements.

Apache Fory™ supports seamless data exchange across Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin.

Xlang Configuration

Rust defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples:

use fory::Fory;

// Use xlang mode
let mut fory = Fory::builder().xlang(true).build();

// Register types with consistent IDs across languages
fory.register::<MyStruct>(100)?;

// Or, on a different Fory instance, use name-based registration
// fory.register_by_name::<MyStruct>("com.example.MyStruct")?;

Type Registration for Xlang

Register by ID

For fast, compact serialization with consistent IDs across languages:

let mut fory = Fory::builder().xlang(true).build();

fory.register::<User>(100)?; // Same ID in Java, Python, etc.

Register by Name

For more flexible type naming:

fory.register_by_name::<User>("com.example.User")?;

Xlang Example

Rust (Serializer)

use fory::Fory;
use fory::ForyStruct;

#[derive(ForyStruct)]
struct Person {
name: String,
age: i32,
}

let mut fory = Fory::builder().xlang(true).build();

fory.register::<Person>(100)?;

let person = Person {
name: "Alice".to_string(),
age: 30,
};

let bytes = fory.serialize(&person)?;
// bytes can be deserialized by Java, Python, etc.

Third-Party Rust Types

An external structural serializer gives a third-party Rust type the same xlang schema as an equivalent local derive:

#[derive(ForyStruct)]
#[fory(target = third_party::User)]
struct UserSerializer {
name: String,
age: u32,
}

let mut fory = Fory::builder().xlang(true).build();
fory.register::<UserSerializer>(100)?;

let bytes = fory.serialize_with::<UserSerializer>(&user)?;

Container roots compose with carrier serializers and keep the ordinary xlang LIST, MAP, tuple, or array representation:

use fory::VecSerializer;

let bytes =
fory.serialize_with::<VecSerializer<UserSerializer>>(&users)?;

Only xlang-representable schemas are accepted. A native Rust enum variant with multiple tuple or named fields is supported with xlang(false), but its serializer registration is rejected in xlang mode. See External-Type Serialization.

Dynamic Rust Carriers

Box<dyn Any>, Rc<dyn Any>, Arc<dyn Any + Send + Sync>, and application dyn Trait carriers can be used in xlang mode when every selected concrete target has an xlang-compatible structural or EXT identity. Fory writes the concrete registered target identity; the Rust trait or erased-carrier identity does not appear on the wire.

Java (Deserializer)

import org.apache.fory.*;
import org.apache.fory.config.*;

public class Person {
public String name;
public int age;
}

Fory fory = Fory.builder()
.withXlang(true)
.withRefTracking(true)
.build();

fory.register(Person.class, 100); // Same ID as Rust

Person person = (Person) fory.deserialize(bytesFromRust);

Python (Deserializer)

import pyfory
from dataclasses import dataclass

@dataclass
class Person:
name: str
age: pyfory.Int32

fory = pyfory.Fory(xlang=True, ref=True)
fory.register_type(Person, type_id=100) # Same ID as Rust

person = fory.deserialize(bytes_from_rust)

Type Mapping

See xlang_type_mapping.md for complete type mapping across languages.

Common Type Mappings

RustJavaPython
i32intint32
i64longint64
f32floatfloat32
f64doublefloat64
Float16Float16float16
BFloat16BFloat16bfloat16
StringStringstr
Vec<T>List<T>List[T]
Vec<Float16>Float16ListFloat16Array
Vec<BFloat16>BFloat16ListBFloat16Array
[Float16; N]Float16ListFloat16Array
[BFloat16; N]BFloat16ListBFloat16Array
HashMap<K,V>Map<K,V>Dict[K,V]
Option<T>nullable TOptional[T]

Lists and Dense Arrays

Rust Vec<T> maps to Fory list<T> by default for manual structs. Use an explicit array field attribute when the schema is dense array<T>.

Fory schemaRust carrier and metadata
list<int32>Vec<i32>
array<bool>#[fory(array)] Vec<bool>
array<int8>#[fory(array)] Vec<i8>
array<int16>#[fory(array)] Vec<i16>
array<int32>#[fory(array)] Vec<i32>
array<int64>#[fory(array)] Vec<i64>
array<uint8>#[fory(array)] Vec<u8>
array<uint16>#[fory(array)] Vec<u16>
array<uint32>#[fory(array)] Vec<u32>
array<uint64>#[fory(array)] Vec<u64>
array<float16>#[fory(array)] Vec<Float16>
array<bfloat16>#[fory(array)] Vec<BFloat16>
array<float32>#[fory(array)] Vec<f32>
array<float64>#[fory(array)] Vec<f64>

Interoperability Best Practices

  1. Use consistent type IDs across all languages
  2. Keep compatible mode for schema evolution
  3. Register all types before serialization
  4. Test cross-language compatibility during development

Specifications and References

Built-in values

use fory::Fory;

fn run() {
let fory = Fory::builder().xlang(true).build();
let bin = fory.serialize(&"hello".to_string()).expect("serialize success");
let obj: String = fory.deserialize(&bin).expect("deserialize success");
assert_eq!("hello".to_string(), obj);
}

Custom values

use chrono::{NaiveDate, NaiveDateTime};
use fory::{Fory, ForyStruct};
use std::collections::HashMap;

#[test]
fn complex_struct() {
#[derive(ForyStruct, Debug, PartialEq)]
struct Animal {
category: String,
}

#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
c1: Vec<u8>, // binary
c2: Vec<i16>, // primitive array
animal: Vec<Animal>,
c3: Vec<Vec<u8>>,
name: String,
c4: HashMap<String, String>,
age: u16,
op: Option<String>,
op2: Option<String>,
date: NaiveDate,
time: NaiveDateTime,
c5: f32,
c6: f64,
}
let person: Person = Person {
c1: vec![1, 2, 3],
c2: vec![5, 6, 7],
c3: vec![vec![1, 2], vec![1, 3]],
animal: vec![Animal {
category: "Dog".to_string(),
}],
c4: HashMap::from([
("hello1".to_string(), "hello2".to_string()),
("hello2".to_string(), "hello3".to_string()),
]),
age: 12,
name: "helo".to_string(),
op: Some("option".to_string()),
op2: None,
date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(),
time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(),
c5: 2.0,
c6: 4.0,
};

let mut fory = Fory::builder().xlang(true).build();
fory
.register_by_name::<Animal>("example.foo2")
.expect("register Animal");
fory
.register_by_name::<Person>("example.foo")
.expect("register Person");
let bin = fory.serialize(&person).expect("serialize success");
let obj: Person = fory.deserialize(&bin).expect("deserialize success");
assert_eq!(person, obj);
}

Shared and circular references

Circular references cannot be implemented in Rust due to ownership restrictions.