Trait Object Serialization
Apache Fory™ supports polymorphic serialization through trait objects, enabling dynamic dispatch and type flexibility.
Supported Trait Object Types
Box<dyn Trait>- Owned trait objectsRc<dyn Trait>- Reference-counted trait objectsArc<dyn Trait>- Thread-safe reference-counted trait objectsVec<Box<dyn Trait>>,HashMap<K, Box<dyn Trait>>- Collections of trait objects
Basic Trait Object Serialization
use fory::{Fory, register_trait_type};
use fory::Serializer;
use fory::ForyStruct;
trait Animal: Serializer {
fn speak(&self) -> String;
fn name(&self) -> &str;
}
#[derive(ForyStruct)]
struct Dog { name: String, breed: String }
impl Animal for Dog {
fn speak(&self) -> String { "Woof!".to_string() }
fn name(&self) -> &str { &self.name }
}
#[derive(ForyStruct)]
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 implementations
register_trait_type!(Animal, Dog, Cat);
#[derive(ForyStruct)]
struct Zoo {
star_animal: Box<dyn Animal>,
}
let mut fory = Fory::builder().xlang(false).build();
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
Apache Fory™ supports serializing Box<dyn Any>, Rc<dyn Any>, and
Arc<dyn Any + Send + Sync> for dynamic type dispatch:
Key points:
- Works with registered concrete non-container types that implement
Serializer - Requires downcasting after deserialization to access the concrete type
- Type information is preserved during serialization
- Useful for plugin systems and dynamic type handling
use std::rc::Rc;
use std::any::Any;
let dog_any: Rc<dyn Any> = Rc::new(Dog {
name: "Rex".to_string(),
breed: "Golden".to_string()
});
// Serialize the Any wrapper
let bytes = fory.serialize(&dog_any)?;
let decoded: Rc<dyn Any> = fory.deserialize(&bytes)?;
// Downcast back to the concrete type
let unwrapped = decoded.downcast_ref::<Dog>().unwrap();
assert_eq!(unwrapped.name, "Rex");
For thread-safe scenarios, use Arc<dyn Any + Send + Sync>:
use std::sync::Arc;
use std::any::Any;
let dog_any: Arc<dyn Any + Send + Sync> = Arc::new(Dog {
name: "Buddy".to_string(),
breed: "Labrador".to_string()
});
let bytes = fory.serialize(&dog_any)?;
let decoded: Arc<dyn Any + Send + Sync> = fory.deserialize(&bytes)?;
// Downcast to concrete type
let unwrapped = decoded.downcast_ref::<Dog>().unwrap();
assert_eq!(unwrapped.name, "Buddy");
Box<dyn Any>, Rc<dyn Any>, and Arc<dyn Any + Send + Sync> are supported
erased Any carriers for registered concrete non-container payloads.
Use Arc<dyn Any + Send + Sync> when the erased payload must be shareable
across threads; the concrete payload type must also satisfy Send + Sync.
Registered structs, enums, and unions that satisfy those bounds can be used as
the erased payload.
The unsupported case is a generic container used directly as the top-level
erased payload. This applies to all erased Any carriers: Box<dyn Any>,
Rc<dyn Any>, and Arc<dyn Any + Send + Sync>. Unsupported direct payloads
include list-, map-, and set-like containers such as Vec<T>, Vec<u8>,
HashMap<K, V>, HashSet<T>, and LinkedList<T>.
If you need to put a container in an erased Any payload, wrap it in a
registered struct, enum, or union and use that wrapper as the erased payload:
use fory::{Fory, ForyStruct};
use std::any::Any;
use std::sync::Arc;
#[derive(ForyStruct)]
struct IntList {
values: Vec<i32>,
}
let mut fory = Fory::builder().xlang(false).build();
fory.register::<IntList>(100)?;
let value: Arc<dyn Any + Send + Sync> = Arc::new(IntList {
values: vec![1, 2, 3],
});
let bytes = fory.serialize(&value)?;
let decoded: Arc<dyn Any + Send + Sync> = fory.deserialize(&bytes)?;
The wrapper makes the erased payload a concrete registered type while the
container remains a normal typed field. The same wrapper model is the supported
path for Box<dyn Any> and Rc<dyn Any>.
Rc/Arc-Based Trait Objects in Structs
For fields with Rc<dyn Trait> or Arc<dyn Trait>, Fory automatically handles the conversion:
use std::sync::Arc;
use std::rc::Rc;
use std::collections::HashMap;
#[derive(ForyStruct)]
struct AnimalShelter {
animals_rc: Vec<Rc<dyn Animal>>,
animals_arc: Vec<Arc<dyn Animal>>,
registry: HashMap<String, Arc<dyn Animal>>,
}
let mut fory = Fory::builder().xlang(false).build();
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(), breed: "Golden".to_string() }),
Rc::new(Cat { name: "Mittens".to_string(), color: "Gray".to_string() }),
],
animals_arc: vec![
Arc::new(Dog { name: "Buddy".to_string(), breed: "Labrador".to_string() }),
],
registry: HashMap::from([
("pet1".to_string(), Arc::new(Dog {
name: "Max".to_string(),
breed: "Shepherd".to_string()
}) as Arc<dyn Animal>),
]),
};
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].speak(), "Woof!");
Standalone Trait Object Serialization
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 for concrete non-container payloads, you can serialize as Box<dyn Any>, Rc<dyn Any>, or Arc<dyn Any + Send + Sync> instead (see the dyn Any section above).
The register_trait_type! macro generates AnimalRc and AnimalArc wrapper types:
// For Rc<dyn Trait>
let dog_rc: Rc<dyn Animal> = Rc::new(Dog {
name: "Rex".to_string(),
breed: "Golden".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(),
breed: "Labrador".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");
Best Practices
- Use
register_trait_type!to register all trait implementations - Keep compatible mode enabled for trait objects
- Register all concrete types before serialization
- Prefer dyn Any for simpler standalone serialization
Related Topics
- References - Rc/Arc shared references
- Schema Evolution - Compatible mode
- Type Registration - Registering types