Expand description
§Apache Fory™ Rust
Apache Fory™ is a blazingly fast multi-language serialization framework powered by JIT compilation and zero-copy techniques, providing up to ultra-fast performance while maintaining ease of use and safety.
The Rust implementation provides versatile and high-performance serialization with automatic memory management and compile-time type safety.
GitHub: https://github.com/apache/fory
§Why Apache Fory™ Rust?
Apache Fory™ Rust solves the fundamental serialization dilemma: you shouldn’t have to choose between performance and developer experience. Traditional frameworks force you to pick between fast but fragile binary formats, flexible but slow text-based protocols, or complex solutions that don’t support your language’s advanced features.
Key differentiators:
- 🔥 Blazingly Fast: Zero-copy deserialization and optimized binary protocols
- 🌍 Cross-Language: Seamlessly serialize/deserialize data across Java, Python, C++, Go, JavaScript, and Rust
- 🎯 Type-Safe: Compile-time type checking with derive macros
- 🔄 Circular References: Automatic tracking of shared and circular references with
Rc/Arcand weak pointers - 🧬 Polymorphic: Serialize trait objects with
Box<dyn Trait>,Rc<dyn Trait>, andArc<dyn Trait> - 📦 Schema Evolution: Compatible mode for independent schema changes
- ⚡ Two Modes: Object graph serialization and zero-copy row-based format
§Quick Start
Add Apache Fory™ to your Cargo.toml:
[dependencies]
fory = "0.13"
fory-derive = "0.13"§Basic Example
use fory::{Fory, Error, Reader};
use fory::ForyObject;
#[derive(ForyObject, Debug, PartialEq)]
struct User {
name: String,
age: i32,
email: String,
}
let mut fory = Fory::default();
fory.register::<User>(1)?;
let user = User {
name: "Alice".to_string(),
age: 30,
email: "alice@example.com".to_string(),
};
// Serialize and deserialize
let bytes = fory.serialize(&user)?;
let decoded: User = fory.deserialize(&bytes)?;
assert_eq!(user, decoded);
// Serialize to specified buffer and deserialize from it
let mut buf: Vec<u8> = vec![];
fory.serialize_to(&user, &mut buf)?;
let mut reader = Reader::new(&buf);
let decoded: User = fory.deserialize_from(&mut reader)?;
assert_eq!(user, decoded);§Core Features
Apache Fory™ Rust provides seven major feature categories, each designed to solve specific serialization challenges in modern applications.
§1. Object Graph Serialization
What it does: Automatically serializes complex nested data structures while preserving relationships and hierarchies.
Why it matters: Most real-world applications deal with complex domain models, not just flat data structures. Apache Fory™ handles arbitrary nesting depth, collections, and optional fields without manual mapping code.
Technical approach: The #[derive(ForyObject)] macro generates efficient
serialization code at compile time using procedural macros. This eliminates runtime
reflection overhead while maintaining type safety.
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::ForyObject;
use std::collections::HashMap;
#[derive(ForyObject, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
address: Address,
hobbies: Vec<String>,
metadata: HashMap<String, String>,
}
#[derive(ForyObject, Debug, PartialEq)]
struct Address {
street: String,
city: String,
country: String,
}
let mut fory = Fory::default();
fory.register::<Address>(100);
fory.register::<Person>(200);
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)?;
let decoded: Person = fory.deserialize(&bytes)?;
assert_eq!(person, decoded);§2. Shared and Circular References
What it does: Automatically tracks and preserves reference identity for shared
objects using Rc<T> and Arc<T>, and handles circular references using weak pointers.
Why it matters: Graph-like data structures (trees, linked lists, object-relational models) are common in real applications but notoriously difficult to serialize. Most frameworks either panic on circular references or require extensive manual handling.
Technical approach: Apache Fory™ maintains a reference tracking map during
serialization. When the same object is encountered multiple times, it’s serialized
only once and subsequent references use IDs. Weak pointers (RcWeak<T>, ArcWeak<T>)
break cycles by serializing as references without strong ownership.
Benefits:
- Space efficiency: No data duplication in serialized output
- Reference identity preservation: Deserialized objects maintain the same sharing relationships
- Circular reference support: Use
RcWeak<T>andArcWeak<T>to break cycles - Forward reference resolution: Callbacks handle weak pointers appearing before targets
§Shared References with Rc/Arc
use fory::Fory;
use fory::Error;
use std::rc::Rc;
let fory = Fory::default();
let shared = Rc::new(String::from("shared_value"));
let data = vec![shared.clone(), shared.clone(), shared.clone()];
let bytes = fory.serialize(&data)?;
let decoded: Vec<Rc<String>> = fory.deserialize(&bytes)?;
assert_eq!(decoded.len(), 3);
assert!(Rc::ptr_eq(&decoded[0], &decoded[1]));
assert!(Rc::ptr_eq(&decoded[1], &decoded[2]));For thread-safe shared references, use Arc<T>:
use fory::Fory;
use fory::Error;
use std::sync::Arc;
let fory = Fory::default();
let shared = Arc::new(String::from("shared_value"));
let data = vec![shared.clone(), shared.clone()];
let bytes = fory.serialize(&data)?;
let decoded: Vec<Arc<String>> = fory.deserialize(&bytes)?;
assert!(Arc::ptr_eq(&decoded[0], &decoded[1]));§Circular References with Weak Pointers
How it works:
- Weak pointers serialize as references to their target objects
- If the strong pointer has been dropped, weak serializes as
Null - Forward references (weak appearing before target) are resolved via callbacks
- All clones of a weak pointer share the same internal cell for automatic updates
use fory::{Fory, Error, RcWeak};
use fory::ForyObject;
use std::rc::Rc;
use std::cell::RefCell;
#[derive(ForyObject, Debug)]
struct Node {
value: i32,
parent: RcWeak<RefCell<Node>>,
children: Vec<Rc<RefCell<Node>>>,
}
let mut fory = Fory::default();
fory.register::<Node>(2000);
let parent = Rc::new(RefCell::new(Node {
value: 1,
parent: RcWeak::new(),
children: vec![],
}));
let child1 = Rc::new(RefCell::new(Node {
value: 2,
parent: RcWeak::from(&parent),
children: vec![],
}));
parent.borrow_mut().children.push(child1.clone());
let bytes = fory.serialize(&parent)?;
let decoded: Rc<RefCell<Node>> = fory.deserialize(&bytes)?;
assert_eq!(decoded.borrow().children.len(), 1);
let upgraded_parent = decoded.borrow().children[0].borrow().parent.upgrade().unwrap();
assert!(Rc::ptr_eq(&decoded, &upgraded_parent));Thread-Safe Circular Graphs with Arc:
use fory::{Fory, Error, ArcWeak};
use fory::ForyObject;
use std::sync::{Arc, Mutex};
#[derive(ForyObject)]
struct Node {
val: i32,
parent: ArcWeak<Mutex<Node>>,
children: Vec<Arc<Mutex<Node>>>,
}
let mut fory = Fory::default();
fory.register::<Node>(6000);
let parent = Arc::new(Mutex::new(Node {
val: 10,
parent: ArcWeak::new(),
children: vec![],
}));
let child = Arc::new(Mutex::new(Node {
val: 20,
parent: ArcWeak::from(&parent),
children: vec![],
}));
parent.lock().unwrap().children.push(child.clone());
let bytes = fory.serialize(&parent)?;
let decoded: Arc<Mutex<Node>> = fory.deserialize(&bytes)?;
assert_eq!(decoded.lock().unwrap().children.len(), 1);§3. Trait Object Serialization
What it does: Enables polymorphic serialization through trait objects, supporting dynamic dispatch and type flexibility.
Why it matters: Rust’s trait system is powerful for abstraction, but serializing
Box<dyn Trait> is notoriously difficult. This feature is essential for plugin systems,
heterogeneous collections, and extensible architectures.
Technical approach: The register_trait_type! macro generates type registration
and dispatch code for trait implementations. During serialization, type IDs are written
alongside data, enabling correct deserialization to the concrete type.
Supported trait object types:
Box<dyn Trait>- Owned trait objectsRc<dyn Trait>- Reference-counted trait objectsArc<dyn Trait>- Thread-safe reference-counted trait objectsRc<dyn Any>/Arc<dyn Any>- Runtime type dispatch without custom traits- Collections:
Vec<Box<dyn Trait>>,HashMap<K, Box<dyn Trait>>
§Basic Trait Object Serialization
use fory::{Fory, register_trait_type, Serializer, Error};
use fory::ForyObject;
trait Animal: Serializer {
fn speak(&self) -> String;
fn name(&self) -> &str;
}
#[derive(ForyObject, Debug)]
struct Dog { name: String, breed: String }
impl Animal for Dog {
fn speak(&self) -> String { "Woof!".to_string() }
fn name(&self) -> &str { &self.name }
}
#[derive(ForyObject, Debug)]
struct Cat { name: String, color: String }
impl Animal for Cat {
fn speak(&self) -> String { "Meow!".to_string() }
fn name(&self) -> &str { &self.name }
}
register_trait_type!(Animal, Dog, Cat);
#[derive(ForyObject)]
struct Zoo {
star_animal: Box<dyn Animal>,
}
let mut fory = Fory::default().compatible(true);
fory.register::<Dog>(100);
fory.register::<Cat>(101);
fory.register::<Zoo>(102);
let zoo = Zoo {
star_animal: Box::new(Dog {
name: "Buddy".to_string(),
breed: "Labrador".to_string(),
}),
};
let bytes = fory.serialize(&zoo)?;
let decoded: Zoo = fory.deserialize(&bytes)?;
assert_eq!(decoded.star_animal.name(), "Buddy");
assert_eq!(decoded.star_animal.speak(), "Woof!");§Serializing dyn Any Trait Objects
What it does: Supports serializing Rc<dyn Any> and Arc<dyn Any> for maximum
runtime type flexibility without defining custom traits.
When to use: Plugin systems, dynamic type handling, or when you need runtime type dispatch without compile-time trait definitions.
Key points:
- Works with any type that implements
Serializer - Requires downcasting after deserialization to access the concrete type
- Type information is preserved during serialization
use fory::Fory;
use fory::Error;
use std::rc::Rc;
use std::any::Any;
use fory::ForyObject;
#[derive(ForyObject)]
struct Dog { name: String }
let mut fory = Fory::default();
fory.register::<Dog>(100);
let dog: Rc<dyn Any> = Rc::new(Dog {
name: "Rex".to_string()
});
let bytes = fory.serialize(&dog)?;
let decoded: Rc<dyn Any> = fory.deserialize(&bytes)?;
let unwrapped = decoded.downcast_ref::<Dog>().unwrap();
assert_eq!(unwrapped.name, "Rex");For thread-safe scenarios, use Arc<dyn Any>:
use fory::Fory;
use fory::Error;
use std::sync::Arc;
use std::any::Any;
use fory::ForyObject;
#[derive(ForyObject)]
struct Cat { name: String }
let mut fory = Fory::default();
fory.register::<Cat>(101);
let cat: Arc<dyn Any> = Arc::new(Cat {
name: "Whiskers".to_string()
});
let bytes = fory.serialize(&cat)?;
let decoded: Arc<dyn Any> = fory.deserialize(&bytes)?;
let unwrapped = decoded.downcast_ref::<Cat>().unwrap();
assert_eq!(unwrapped.name, "Whiskers");§Rc/Arc-Based Trait Objects in Structs
For struct fields containing Rc<dyn Trait> or Arc<dyn Trait>, Apache Fory™
automatically handles the conversion without needing wrappers:
use fory::{Fory, register_trait_type, Serializer, Error};
use fory::ForyObject;
use std::sync::Arc;
use std::rc::Rc;
trait Animal: Serializer {
fn name(&self) -> &str;
}
#[derive(ForyObject, Debug)]
struct Dog { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
}
#[derive(ForyObject, Debug)]
struct Cat { name: String }
impl Animal for Cat {
fn name(&self) -> &str { &self.name }
}
register_trait_type!(Animal, Dog, Cat);
#[derive(ForyObject)]
struct AnimalShelter {
animals_rc: Vec<Rc<dyn Animal>>,
animals_arc: Vec<Arc<dyn Animal>>,
}
let mut fory = Fory::default().compatible(true);
fory.register::<Dog>(100);
fory.register::<Cat>(101);
fory.register::<AnimalShelter>(102);
let shelter = AnimalShelter {
animals_rc: vec![
Rc::new(Dog { name: "Rex".to_string() }),
Rc::new(Cat { name: "Mittens".to_string() }),
],
animals_arc: vec![
Arc::new(Dog { name: "Buddy".to_string() }),
],
};
let bytes = fory.serialize(&shelter)?;
let decoded: AnimalShelter = fory.deserialize(&bytes)?;
assert_eq!(decoded.animals_rc[0].name(), "Rex");
assert_eq!(decoded.animals_arc[0].name(), "Buddy");§Standalone Trait Object Serialization with Wrappers
Due to Rust’s orphan rule, Rc<dyn Trait> and Arc<dyn Trait> cannot implement
Serializer directly. For standalone serialization (not inside struct fields),
the register_trait_type! macro generates wrapper types.
Note: If you don’t want to use wrapper types, you can serialize as Rc<dyn Any>
or Arc<dyn Any> instead (see the dyn Any section above).
The register_trait_type! macro generates AnimalRc and AnimalArc wrapper types:
use fory::{Fory, Error, register_trait_type, Serializer};
use fory::ForyObject;
use std::sync::Arc;
use std::rc::Rc;
trait Animal: Serializer {
fn name(&self) -> &str;
}
#[derive(ForyObject, Debug)]
struct Dog { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
}
register_trait_type!(Animal, Dog);
let mut fory = Fory::default().compatible(true);
fory.register::<Dog>(100);
// For Rc<dyn Trait>
let dog_rc: Rc<dyn Animal> = Rc::new(Dog { name: "Rex".to_string() });
let wrapper = AnimalRc::from(dog_rc);
let bytes = fory.serialize(&wrapper)?;
let decoded: AnimalRc = fory.deserialize(&bytes)?;
// Unwrap back to Rc<dyn Animal>
let unwrapped: Rc<dyn Animal> = decoded.unwrap();
assert_eq!(unwrapped.name(), "Rex");
// For Arc<dyn Trait>
let dog_arc: Arc<dyn Animal> = Arc::new(Dog { name: "Buddy".to_string() });
let wrapper = AnimalArc::from(dog_arc);
let bytes = fory.serialize(&wrapper)?;
let decoded: AnimalArc = fory.deserialize(&bytes)?;
let unwrapped: Arc<dyn Animal> = decoded.unwrap();
assert_eq!(unwrapped.name(), "Buddy");§4. Schema Evolution
What it does: Supports schema evolution in Compatible mode, allowing serialization and deserialization peers to have different type definitions.
Why it matters: In distributed systems and microservices, different services evolve independently. Schema evolution enables zero-downtime deployments where services can be updated gradually without breaking communication.
Technical approach: In Compatible mode, Apache Fory™ includes field names and type metadata in the serialized data. During deserialization, fields are matched by name, allowing for additions, deletions, and reordering.
Features:
- Add new fields with default values
- Remove obsolete fields (skipped during deserialization)
- Change field nullability (
T↔Option<T>) - Reorder fields (matched by name, not position)
- Type-safe fallback to default values for missing fields
Compatibility rules:
- Field names must match (case-sensitive)
- Type changes are not supported (except nullable/non-nullable)
- Nested struct types must be registered on both sides
use fory::{Fory, Error};
use fory::ForyObject;
use std::collections::HashMap;
#[derive(ForyObject, Debug)]
struct PersonV1 {
name: String,
age: i32,
address: String,
}
#[derive(ForyObject, Debug)]
struct PersonV2 {
name: String,
age: i32,
phone: Option<String>,
metadata: HashMap<String, String>,
}
let mut fory1 = Fory::default().compatible(true);
fory1.register::<PersonV1>(1);
let mut fory2 = Fory::default().compatible(true);
fory2.register::<PersonV2>(1);
let person_v1 = PersonV1 {
name: "Alice".to_string(),
age: 30,
address: "123 Main St".to_string(),
};
let bytes = fory1.serialize(&person_v1)?;
let person_v2: PersonV2 = fory2.deserialize(&bytes)?;
assert_eq!(person_v2.name, "Alice");
assert_eq!(person_v2.age, 30);
assert_eq!(person_v2.phone, None);§5. Enum Support
What it does: Supports C-style enums (enums without data payloads) with efficient varint encoding.
Why it matters: Enums are common for state machines, status codes, and type discriminators. Efficient encoding and schema evolution support are essential.
Technical approach: Each variant is assigned an ordinal value (0, 1, 2, …) during serialization. Ordinals are encoded using variable-length integers for space efficiency.
Features:
- Efficient varint encoding for ordinals
- Schema evolution support in Compatible mode
- Type-safe variant matching
- Default variant support with
#[default]
use fory::Fory;
use fory::Error;
use fory::ForyObject;
#[derive(ForyObject, Debug, PartialEq, Default)]
enum Status {
#[default]
Pending,
Active,
Inactive,
Deleted,
}
let mut fory = Fory::default();
fory.register::<Status>(1);
let status = Status::Active;
let bytes = fory.serialize(&status)?;
let decoded: Status = fory.deserialize(&bytes)?;
assert_eq!(status, decoded);§6. Custom Serializers
What it does: Allows manual implementation of the Serializer trait for types
that don’t support #[derive(ForyObject)].
When to use:
- External types from other crates that you can’t modify
- Types with special serialization requirements
- Legacy data format compatibility
- Performance-critical custom encoding
- Complex types that require special handling
Technical approach: Implement the Serializer trait’s fory_write_data() and
fory_read_data() methods to control exactly how data is written to and read from
the binary buffer.
use fory::{Fory, TypeResolver, ReadContext, WriteContext, Serializer, ForyDefault, Error};
use std::any::Any;
#[derive(Debug, PartialEq, Default)]
struct CustomType {
value: i32,
name: String,
}
impl Serializer for CustomType {
fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> {
context.writer.write_i32(self.value);
context.writer.write_varuint32(self.name.len() as u32);
context.writer.write_utf8_string(&self.name);
Ok(())
}
fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
let value = context.reader.read_i32()?;
let len = context.reader.read_varuint32()? as usize;
let name = context.reader.read_utf8_string(len)?;
Ok(Self { value, name })
}
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<u32, Error> {
Self::fory_get_type_id(type_resolver)
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl ForyDefault for CustomType {
fn fory_default() -> Self {
Self::default()
}
}
let mut fory = Fory::default();
fory.register_serializer::<CustomType>(100);
let custom = CustomType {
value: 42,
name: "test".to_string(),
};
let bytes = fory.serialize(&custom)?;
let decoded: CustomType = fory.deserialize(&bytes)?;
assert_eq!(custom, decoded);§7. Row-Based Serialization
What it does: Provides a high-performance row format for zero-copy deserialization, enabling random access to fields directly from binary data without full object reconstruction.
Why it matters: Traditional serialization reconstructs entire objects in memory. For analytics workloads or when you only need a few fields from large objects, this is wasteful. Row format provides O(1) field access without deserialization.
Technical approach: Fields are encoded in a binary row with fixed offsets for primitives. Variable-length data (strings, collections) are stored with offset pointers. A null bitmap tracks which fields are present. The generated code provides accessor methods that read directly from the binary buffer.
Key benefits:
- Zero-copy access: Read fields without allocating or copying data
- Partial deserialization: Access only the fields you need
- Memory-mapped files: Work with data larger than RAM
- Cache-friendly: Sequential memory layout for better CPU cache utilization
- Lazy evaluation: Defer expensive operations until field access
When to use row format:
- Analytics workloads with selective field access
- Large datasets where only a subset of fields is needed
- Memory-constrained environments
- High-throughput data pipelines
- Reading from memory-mapped files or shared memory
Performance characteristics:
| Operation | Object Format | Row Format |
|---|---|---|
| Full deserialization | Allocates all objects | Zero allocation |
| Single field access | Full deserialization required | Direct offset read (O(1)) |
| Memory usage | Full object graph in memory | Only accessed fields in memory |
| Suitable for | Small objects, full access | Large objects, selective access |
use fory::{to_row, from_row};
use fory_derive::ForyRow;
use std::collections::BTreeMap;
#[derive(ForyRow)]
struct UserProfile {
id: i64,
username: String,
email: String,
scores: Vec<i32>,
preferences: BTreeMap<String, String>,
is_active: bool,
}
let profile = UserProfile {
id: 12345,
username: "alice".to_string(),
email: "alice@example.com".to_string(),
scores: vec![95, 87, 92, 88],
preferences: BTreeMap::from([
("theme".to_string(), "dark".to_string()),
("language".to_string(), "en".to_string()),
]),
is_active: true,
};
let row_data = to_row(&profile).unwrap();
let row = from_row::<UserProfile>(&row_data);
assert_eq!(row.id(), 12345);
assert_eq!(row.username(), "alice");
assert_eq!(row.is_active(), true);
let scores = row.scores();
assert_eq!(scores.size(), 4);
assert_eq!(scores.get(0), 95);§Supported Types
Apache Fory™ supports a comprehensive type system for maximum flexibility.
§Primitive Types
bool- Boolean valuesi8,i16,i32,i64- Signed integersf32,f64- Floating point numbersString- UTF-8 encoded strings
§Collections
Vec<T>- Dynamic arraysHashMap<K, V>- Hash-based mapsBTreeMap<K, V>- Ordered mapsHashSet<T>- Hash-based setsOption<T>- Optional values
§Smart Pointers
Box<T>- Heap allocationRc<T>- Reference counting (shared references tracked automatically)Arc<T>- Thread-safe reference counting (shared references tracked)RcWeak<T>- Weak reference toRc<T>(breaks circular references)ArcWeak<T>- Weak reference toArc<T>(breaks circular references)RefCell<T>- Interior mutability with runtime borrow checkingMutex<T>- Thread-safe interior mutability
§Date and Time (requires chrono feature)
chrono::NaiveDate- Date without timezonechrono::NaiveDateTime- Timestamp without timezone
§Custom Types
- Structs with
#[derive(ForyObject)]- Object graph serialization - Structs with
#[derive(ForyRow)]- Row-based serialization - C-style enums with
#[derive(ForyObject)]- Enum support - Manual
Serializerimplementation - Custom serialization logic
§Trait Objects
Box<dyn Trait>- Owned trait objectsRc<dyn Trait>- Reference-counted trait objectsArc<dyn Trait>- Thread-safe reference-counted trait objectsRc<dyn Any>- Runtime type dispatch without custom traitsArc<dyn Any>- Thread-safe runtime type dispatch
§Serialization Modes
Apache Fory™ supports two serialization modes to balance between performance and flexibility:
§SchemaConsistent Mode (Default)
When to use: Maximum performance when schemas are guaranteed to match.
Characteristics:
- Type declarations must match exactly between serialization and deserialization
- Smaller payload size (no field names or metadata)
- Faster serialization and deserialization
- Suitable for monolithic applications or tightly coupled services
use fory::Fory;
let fory = Fory::default();§Compatible Mode
When to use: Schema evolution in distributed systems or microservices.
Characteristics:
- Type declarations can differ between peers
- Allows field additions, deletions, and reordering
- Larger payload size (includes field names and metadata)
- Slightly slower due to metadata processing
- Essential for zero-downtime deployments
use fory::Fory;
let fory = Fory::default().compatible(true);§Cross-Language Serialization
What it enables: Seamless data exchange across Java, Python, C++, Go, JavaScript, and Rust implementations.
Why it matters: Microservices architectures often use multiple languages. Apache Fory™ provides a common binary protocol without IDL files or code generation.
How to enable:
use fory::Fory;
use fory::ForyObject;
let mut fory = Fory::default()
.compatible(true)
.xlang(true);
#[derive(ForyObject)]
struct MyStruct {
field1: i32,
field2: String,
}
fory.register_by_namespace::<MyStruct>("com.example", "MyStruct");Type registration strategies:
- ID-based registration:
fory.register::<T>(id)- Fastest, requires coordination - Namespace-based registration:
fory.register_by_namespace::<T>(namespace, name)- Automatic cross-language mapping
§Performance Characteristics
Apache Fory™ Rust is designed for maximum performance through multiple techniques:
Compile-time code generation:
- Procedural macros generate specialized serialization code
- Zero runtime overhead, no reflection
- Monomorphization for type-specific optimizations
Zero-copy techniques:
- Row format enables direct memory access
- No intermediate object allocation
- Memory-mapped file support
Space efficiency:
- Variable-length integer encoding
- Reference deduplication (shared objects serialized once)
- Compact binary format
Buffer management:
- Pre-allocation based on
fory_reserved_space()hints - Minimized reallocations
- Little-endian layout for modern CPUs
§Error Handling
Apache Fory™ uses Result<T, Error> for all fallible operations, providing
comprehensive error handling:
use fory::{Fory, Error};
use fory::ForyObject;
#[derive(ForyObject)]
struct Data {
value: i32,
}
fn process_data(bytes: &[u8]) -> Result<Data, Error> {
let mut fory = Fory::default();
fory.register::<Data>(100);
let data: Data = fory.deserialize(bytes)?;
Ok(data)
}§Thread Safety
Fory implements Send and Sync, so a single instance can be shared across threads
(for example via Arc<Fory>) for concurrent serialization and deserialization. The
internal context pools grow lazily and rely on thread-safe primitives, allowing multiple
workers to reuse buffers without additional coordination.
use std::sync::Arc;
use std::thread;
use fory::Fory;
use fory::ForyObject;
#[derive(ForyObject, Clone, Copy, Debug)]
struct Item {
value: i32,
}
let mut fory = Fory::default();
fory.register::<Item>(1000).unwrap();
let fory = Arc::new(fory);
let handles: Vec<_> = (0..8)
.map(|i| {
let shared = Arc::clone(&fory);
thread::spawn(move || {
let item = Item { value: i };
shared.serialize(&item).unwrap()
})
})
.collect();
for handle in handles {
let bytes = handle.join().unwrap();
let item: Item = fory.deserialize(&bytes).unwrap();
assert!(item.value >= 0);
}Best practice: Perform type registration (e.g., fory.register::<T>(id)) before
spawning worker threads so metadata is ready, then share the configured instance.
§Examples
See the tests/ directory for comprehensive examples:
tests/tests/test_complex_struct.rs- Complex nested structurestests/tests/test_rc_arc_trait_object.rs- Trait object serializationtests/tests/test_weak.rs- Circular reference handlingtests/tests/test_cross_language.rs- Cross-language compatibility
§Troubleshooting
- Type registry errors: Errors such as
TypeId ... not found in type_info registrymean the type was never registered with the activeForyinstance. Ensure every serializable struct, enum, or trait implementation callsregister::<T>(type_id)before use, and reuse the same IDs when deserializing. - Quick error lookup: Always prefer the static constructors on
fory_core::error::Error—for exampleError::type_mismatch,Error::invalid_data, orError::unknown. They keep diagnostics consistent and allow optional panic-on-error debugging. - Panic on error for backtraces: Set
FORY_PANIC_ON_ERROR=1(ortrue) together withRUST_BACKTRACE=1while running tests or binaries to panic exactly where an error is constructed. Unset the variable afterwards so production paths keep returningResult. - Struct field tracing: Add the
#[fory_debug]attribute next to#[derive(ForyObject)]when you need per-field instrumentation. Once compiled with debug hooks, callset_before_write_field_func,set_after_write_field_func,set_before_read_field_func, orset_after_read_field_funcfromfory_core::serializer::struct_to install custom callbacks, and usereset_struct_debug_hooks()to restore defaults. - Lightweight logging: If custom callbacks are unnecessary, enable
ENABLE_FORY_DEBUG_OUTPUT=1to have the default hook handlers print field-level read/write events. This is useful for spotting cursor misalignment or unexpected buffer growth. - Test-time hygiene: Some integration tests expect
FORY_PANIC_ON_ERRORto stay unset. Export it only during focused debugging, and rely on targeted commands such ascargo test --features tests -p tests --test <case>when isolating failures.
§Documentation
- Protocol Specification - Binary protocol details
- Row Format Specification - Row format internals
- Type Mapping - Cross-language type mappings
- API Documentation - Complete API reference
- GitHub Repository - Source code and issue tracking
Macros§
- register_
trait_ type - Macro to register trait object conversions for custom traits.
Structs§
- ArcWeak
- A serializable wrapper around
std::sync::Weak<T>(thread-safe). - Fory
- The main Fory serialization framework instance.
- RcWeak
- A serializable wrapper around
std::rc::Weak<T>. - Read
Context - Deserialization state container used on a single thread at a time. Sharing the same instance across threads simultaneously causes undefined behavior.
- Reader
- Type
Resolver - TypeResolver is a resolver for fast type/serializer dispatch.
- Write
Context - Serialization state container used on a single thread at a time. Sharing the same instance across threads simultaneously causes undefined behavior.
- Writer
Enums§
Traits§
- Fory
Default - Trait for creating default values during Fory deserialization.
- Serializer
- Core trait for Fory serialization and deserialization.
Functions§
Derive Macros§
- Fory
Object - Derive macro for object serialization.
- ForyRow
- Derive macro for row-based serialization.