Skip to content

Commit 0ec7261

Browse files
authored
refactor(rust): refactor sync send type (#3737)
## Why? ## What does this PR do? ## Related issues #3732 #3736 ## AI Contribution Checklist - [ ] Substantial AI assistance was used in this PR: `yes` / `no` - [ ] If `yes`, I included a completed [AI Contribution Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs) in this PR description and the required `AI Usage Disclosure`. - [ ] If `yes`, my PR description includes the required `ai_review` summary and screenshot evidence of the final clean AI review results from both fresh reviewers on the current PR diff or current HEAD after the latest code changes. ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark
1 parent e637cf7 commit 0ec7261

35 files changed

Lines changed: 1328 additions & 543 deletions

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
6868
- User guide docs must explain user-visible behavior, commands, and examples.
6969
Do not add implementation details, internal ownership rationale, build flags,
7070
or type-id-space caveats unless they directly clarify a confusion users can
71-
act on.
71+
act on. Translate internal owner-model details into concrete user actions, and
72+
avoid phrases such as "serializer-owned capability" or "registration alone
73+
does not..." in user-facing docs.
7274
- Add comments only when behavior is hard to understand or an algorithm is non-obvious.
7375
- Do not remove existing code comments unless they are stale, misleading, redundant, or no longer necessary after the change.
7476
- Only add tests that verify internal behaviors or fix specific bugs; do not create unnecessary tests unless requested.

docs/guide/rust/custom-serializers.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer`
3131
## Implementing the Serializer Trait
3232

3333
```rust
34-
use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error};
34+
use fory::{Error, Fory, ForyDefault, ReadContext, Serializer, TypeResolver, WriteContext};
3535
use std::any::Any;
3636

3737
#[derive(Debug, PartialEq, Default)]
@@ -41,20 +41,21 @@ struct CustomType {
4141
}
4242

4343
impl Serializer for CustomType {
44-
fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
44+
fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> {
4545
context.writer.write_i32(self.value);
46-
context.writer.write_varuint32(self.name.len() as u32);
46+
context.writer.write_var_u32(self.name.len() as u32);
4747
context.writer.write_utf8_string(&self.name);
48+
Ok(())
4849
}
4950

50-
fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result<Self, Error> {
51-
let value = context.reader.read_i32();
52-
let len = context.reader.read_varuint32() as usize;
53-
let name = context.reader.read_utf8_string(len);
51+
fn fory_read_data(context: &mut ReadContext) -> Result<Self, Error> {
52+
let value = context.reader.read_i32()?;
53+
let len = context.reader.read_var_u32()? as usize;
54+
let name = context.reader.read_utf8_string(len)?;
5455
Ok(Self { value, name })
5556
}
5657

57-
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> u32 {
58+
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<fory::TypeId, Error> {
5859
Self::fory_get_type_id(type_resolver)
5960
}
6061

@@ -76,6 +77,28 @@ impl ForyDefault for CustomType {
7677
>
7778
> **Tip**: If your type supports `#[derive(ForyStruct)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations.
7879
80+
## Manual Serializers and Arc Any
81+
82+
If a manually registered serializer needs its type to round-trip behind
83+
`Arc<dyn Any + Send + Sync>` or preserve `UnknownCase` payloads, implement the
84+
send-sync Any reader and return the concrete value as a boxed `Any` value:
85+
86+
```rust
87+
impl Serializer for CustomType {
88+
fn fory_read_data_as_send_sync_any(
89+
context: &mut ReadContext,
90+
) -> Result<Box<dyn Any + Send + Sync>, Error> {
91+
Ok(Box::new(Self::fory_read_data(context)?))
92+
}
93+
94+
// Implement the ordinary Serializer methods as shown above.
95+
// ...
96+
}
97+
```
98+
99+
Do not override this method for values that contain fields whose types are not
100+
`Send + Sync`, such as `Rc<T>`, `RcWeak<T>`, `RefCell<T>`, or `Cell<T>`.
101+
79102
## Registering Custom Serializers
80103

81104
```rust

docs/guide/rust/native-serialization.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ Native serialization owns the Rust-specific object surface:
100100
- `Box<T>`, `Rc<T>`, `Arc<T>`, `RcWeak<T>`, and `ArcWeak<T>`.
101101
- `RefCell<T>` and `Mutex<T>`.
102102
- Trait objects such as `Box<dyn Trait>`, `Rc<dyn Trait>`, and `Arc<dyn Trait>`.
103-
- Runtime type dispatch with `Rc<dyn Any>` and `Arc<dyn Any + Send + Sync>`.
103+
- Runtime type dispatch with `Box<dyn Any>`, `Rc<dyn Any>`, and
104+
`Arc<dyn Any + Send + Sync>` for registered non-container payloads. Wrap
105+
containers in registered structs, enums, or unions before using them behind
106+
erased `Any` carriers.
104107
- Date and time carriers, including optional `chrono` support.
105108

106109
Use [Basic Serialization](basic-serialization.md), [References](references.md), and

docs/guide/rust/polymorphism.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ assert_eq!(decoded.star_animal.speak(), "Woof!");
8585

8686
## Serializing dyn Any Trait Objects
8787

88-
Apache Fory™ supports serializing `Rc<dyn Any>` and
88+
Apache Fory™ supports serializing `Box<dyn Any>`, `Rc<dyn Any>`, and
8989
`Arc<dyn Any + Send + Sync>` for runtime type dispatch:
9090

9191
**Key points:**
9292

93-
- Works with any type that implements `Serializer`
93+
- Works with registered concrete non-container types that implement `Serializer`
9494
- Requires downcasting after deserialization to access the concrete type
9595
- Type information is preserved during serialization
9696
- Useful for plugin systems and dynamic type handling
@@ -132,6 +132,46 @@ let unwrapped = decoded.downcast_ref::<Dog>().unwrap();
132132
assert_eq!(unwrapped.name, "Buddy");
133133
```
134134

135+
`Box<dyn Any>`, `Rc<dyn Any>`, and `Arc<dyn Any + Send + Sync>` are supported
136+
erased `Any` carriers for registered concrete non-container payloads.
137+
Use `Arc<dyn Any + Send + Sync>` when the erased payload must be shareable
138+
across threads; the concrete payload type must also satisfy `Send + Sync`.
139+
Registered structs, enums, and unions that satisfy those bounds can be used as
140+
the erased payload.
141+
142+
The unsupported case is a generic container used directly as the top-level
143+
erased payload. This applies to all erased `Any` carriers: `Box<dyn Any>`,
144+
`Rc<dyn Any>`, and `Arc<dyn Any + Send + Sync>`. Unsupported direct payloads
145+
include list-, map-, and set-like containers such as `Vec<T>`, `Vec<u8>`,
146+
`HashMap<K, V>`, `HashSet<T>`, and `LinkedList<T>`.
147+
148+
If you need to put a container in an erased `Any` payload, wrap it in a
149+
registered struct, enum, or union and use that wrapper as the erased payload:
150+
151+
```rust
152+
use fory::{Fory, ForyStruct};
153+
use std::any::Any;
154+
use std::sync::Arc;
155+
156+
#[derive(ForyStruct)]
157+
struct IntList {
158+
values: Vec<i32>,
159+
}
160+
161+
let mut fory = Fory::builder().xlang(false).build();
162+
fory.register::<IntList>(100)?;
163+
164+
let value: Arc<dyn Any + Send + Sync> = Arc::new(IntList {
165+
values: vec![1, 2, 3],
166+
});
167+
let bytes = fory.serialize(&value)?;
168+
let decoded: Arc<dyn Any + Send + Sync> = fory.deserialize(&bytes)?;
169+
```
170+
171+
The wrapper makes the erased payload a concrete registered type while the
172+
container remains a normal typed field. The same wrapper model is the supported
173+
path for `Box<dyn Any>` and `Rc<dyn Any>`.
174+
135175
## Rc/Arc-Based Trait Objects in Structs
136176

137177
For fields with `Rc<dyn Trait>` or `Arc<dyn Trait>`, Fory automatically handles the conversion:
@@ -180,7 +220,7 @@ assert_eq!(decoded.animals_arc[0].speak(), "Woof!");
180220

181221
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.
182222

183-
**Note:** If you don't want to use wrapper types, you can serialize as `Rc<dyn Any>` or `Arc<dyn Any + Send + Sync>` instead (see the dyn Any section above).
223+
**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).
184224

185225
The `register_trait_type!` macro generates `AnimalRc` and `AnimalArc` wrapper types:
186226

docs/guide/rust/schema-evolution.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,16 @@ let decoded: Value = fory.deserialize(&bytes)?;
129129
assert_eq!(value, decoded);
130130
```
131131

132-
For typed ADT unions whose schema cases are unit or single-payload variants,
133-
`#[fory(unknown)] Unknown(::fory::UnknownCase)` is only the runtime
134-
forward-compatibility carrier. It cannot be the default variant, and the union
135-
must include at least one real schema case. The marker only selects the carrier
136-
and does not add an entry to the schema case table; schema cases use
137-
non-negative IDs. `UnknownCase` stores its payload as
138-
`Arc<dyn Any + Send + Sync>`, so locally registered future payload types must be
139-
thread-safe to be preserved as unknown cases.
132+
For typed ADT unions whose cases are unit or single-payload variants, add an
133+
`#[fory(unknown)] Unknown(::fory::UnknownCase)` variant when you need to
134+
preserve future payload variants. Do not make the unknown variant the default;
135+
keep a real schema case marked `#[fory(default)]`. Register future payload types
136+
locally before deserializing unknown cases you need to preserve.
137+
138+
`UnknownCase` stores its payload as `Arc<dyn Any + Send + Sync>`, so preserved
139+
payload types must satisfy `Send + Sync`. Direct generic containers are not
140+
supported as erased `Any` payloads; wrap the container in a registered derived
141+
type if an unknown case needs to preserve it.
140142

141143
### Enum Schema Evolution
142144

rust/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,18 @@ The examples in this section use native mode because Rust trait objects and `dyn
251251
- `Box<dyn Any>`/`Rc<dyn Any>`/`Arc<dyn Any + Send + Sync>` - Any trait type objects
252252
- `Vec<Box<dyn Trait>>`, `HashMap<K, Box<dyn Trait>>` - Collections of trait objects
253253

254+
`Box<dyn Any>`, `Rc<dyn Any>`, and `Arc<dyn Any + Send + Sync>` are supported
255+
erased `Any` carriers for registered concrete non-container payloads.
256+
Use `Arc<dyn Any + Send + Sync>` when the erased payload must be shareable
257+
across threads; the concrete payload type must also satisfy `Send + Sync`.
258+
Registered structs, enums, and unions that satisfy those bounds can be used as
259+
the erased payload.
260+
Generic containers such as `Vec<T>`, `HashMap<K, V>`, `HashSet<T>`, and
261+
`LinkedList<T>` are not supported directly as top-level erased `Any` payloads
262+
behind any of those carriers. This also includes primitive vector encodings such
263+
as `Vec<u8>`. Wrap the container in a registered derived type when it needs to
264+
travel behind an erased `Any` carrier.
265+
254266
**Basic Trait Object Serialization Example:**
255267

256268
```rust

rust/fory-core/src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,18 @@ impl Error {
552552
}
553553
}
554554

555+
#[cold]
556+
#[inline(never)]
557+
pub(crate) fn unsupported_send_sync_type<T>() -> Error
558+
where
559+
T: ?Sized,
560+
{
561+
Error::type_error(format!(
562+
"{} cannot be represented as Arc<dyn Any + Send + Sync>",
563+
std::any::type_name::<T>()
564+
))
565+
}
566+
555567
/// Ensures a condition is true; otherwise returns an [`enum@Error`].
556568
///
557569
/// # Examples

rust/fory-core/src/fory.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -954,16 +954,6 @@ impl Fory {
954954
.register_serializer_by_name::<T>(namespace, type_name)
955955
}
956956

957-
/// Registers a generic trait object type for serialization.
958-
/// This method should be used to register collection types such as `Vec<T>`, `HashMap<K, V>`, etc.
959-
/// Don't register concrete struct types with this method. Use `register()` instead.
960-
pub fn register_generic_trait<T: 'static + Serializer + ForyDefault>(
961-
&mut self,
962-
) -> Result<(), Error> {
963-
self.check_registration_allowed()?;
964-
self.type_resolver.register_generic_trait::<T>()
965-
}
966-
967957
/// Writes the serialization header to the writer.
968958
#[inline(always)]
969959
pub fn write_head<T: Serializer>(&self, writer: &mut Writer) {

0 commit comments

Comments
 (0)