blob: 47ac16ede8e55d2f892b58c14d096158a00ff593 [file] [view]
---
title: Trait Object Serialization
sidebar_position: 9
id: polymorphism
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---
Apache Fory supports dynamic values through registered application traits and
the standard `Any` trait.
## Application Traits
An application trait extends `ForyObject`. List every concrete target accepted
by the trait in `register_trait_type!`:
```rust
use fory::{
register_trait_type, Fory, ForyObject, ForyStruct,
};
trait Animal: ForyObject {
fn name(&self) -> &str;
}
#[derive(ForyStruct)]
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
}
#[derive(ForyStruct)]
struct Cat {
name: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
}
register_trait_type!(Animal, Dog, Cat);
```
The concrete list contains runtime value types. Each type must also be
registered with the `Fory` instance:
```rust
let mut fory = Fory::builder().xlang(false).build();
fory.register::<Dog>(100)?;
fory.register::<Cat>(101)?;
```
A listed third-party target can be registered through an external structural
serializer or custom serializer. The list still names the target, not that
serializer.
The generated names are private to the macro's module by default. A library
that exports the generated root serializers adds normal Rust visibility:
```rust
register_trait_type!(pub Animal, Dog, Cat);
```
The trait and listed types must be visible at least as broadly as the generated
serializers. `pub(crate)` and restricted visibility are also supported.
## Trait Object Fields
`Box<dyn Trait>` and `Rc<dyn Trait>` work directly in derived fields and nested
containers:
```rust
use std::collections::HashMap;
use std::rc::Rc;
#[derive(ForyStruct)]
struct Shelter {
featured: Box<dyn Animal>,
shared: Rc<dyn Animal>,
animals: Vec<Box<dyn Animal>>,
by_name: HashMap<String, Rc<dyn Animal>>,
}
```
Fory checks the closed concrete list before materializing a value. Do not add
`#[fory(with = ...)]` to a trait-object node; its concrete serializer is chosen
dynamically from the registered target.
For `Arc<dyn Trait>`, the trait must be thread-safe and the macro uses its sync
form:
```rust
use std::sync::Arc;
trait SharedAnimal: ForyObject + Send + Sync {
fn name(&self) -> &str;
}
impl SharedAnimal for Dog {
fn name(&self) -> &str {
&self.name
}
}
impl SharedAnimal for Cat {
fn name(&self) -> &str {
&self.name
}
}
register_trait_type!(sync SharedAnimal, Dog, Cat);
#[derive(ForyStruct)]
struct SharedShelter {
featured: Arc<dyn SharedAnimal>,
}
```
Every listed target in a sync declaration must implement `Send + Sync`.
Use `register_trait_type!(pub sync SharedAnimal, Dog, Cat)` when a library
exports the generated root serializers.
## Trait Object Roots
`Box<dyn Trait>` is an ordinary root:
```rust
let animal: Box<dyn Animal> = Box::new(Dog {
name: "Rex".to_string(),
});
let bytes = fory.serialize(&animal)?;
let decoded: Box<dyn Animal> = fory.deserialize(&bytes)?;
assert_eq!(decoded.name(), "Rex");
```
Rust's orphan rules prevent an ordinary serializer implementation directly on
`Rc<dyn Trait>` and `Arc<dyn Trait>`. The macro generates serializer types for
those roots:
```rust
let animal: Rc<dyn Animal> = Rc::new(Dog {
name: "Milo".to_string(),
});
let bytes =
fory.serialize_with::<AnimalRcSerializer>(&animal)?;
let decoded =
fory.deserialize_with::<AnimalRcSerializer>(&bytes)?;
assert_eq!(decoded.name(), "Milo");
```
```rust
let animal: Arc<dyn SharedAnimal> = Arc::new(Dog {
name: "Luna".to_string(),
});
let bytes =
fory.serialize_with::<SharedAnimalArcSerializer>(&animal)?;
let decoded =
fory.deserialize_with::<SharedAnimalArcSerializer>(&bytes)?;
assert_eq!(decoded.name(), "Luna");
```
These APIs serialize the original `Rc` or `Arc`; there is no conversion wrapper.
The generated serializer types are not registered by ID or name.
## Dynamic `Any`
Fory supports:
- `Box<dyn Any>`;
- `Rc<dyn Any>`;
- `Arc<dyn Any + Send + Sync>`.
The concrete target must be registered:
```rust
use std::any::Any;
let value: Rc<dyn Any> = Rc::new(Dog {
name: "Rex".to_string(),
});
let bytes = fory.serialize(&value)?;
let decoded: Rc<dyn Any> = fory.deserialize(&bytes)?;
let dog = decoded.downcast_ref::<Dog>().unwrap();
assert_eq!(dog.name, "Rex");
```
Use `Arc<dyn Any + Send + Sync>` when the erased value must be shared across
threads:
```rust
let value: Arc<dyn Any + Send + Sync> = Arc::new(Dog {
name: "Buddy".to_string(),
});
let bytes = fory.serialize(&value)?;
let decoded: Arc<dyn Any + Send + Sync> =
fory.deserialize(&bytes)?;
let dog = decoded.downcast_ref::<Dog>().unwrap();
assert_eq!(dog.name, "Buddy");
```
Derived serializers support synchronized `Arc` materialization when their
targets satisfy `Send + Sync`. A custom serializer that needs this path
implements `read_arc_any`.
Generic LIST, SET, and MAP identities do not identify one exact Rust generic
target behind `Any`. Put such a container in a registered struct, or register an
exact whole-container custom serializer when an opaque EXT representation is
intentional.
## Related Topics
- [External-Type Serialization](external-types.md)
- [References](references.md)
- [Type Registration](type-registration.md)