This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 29257eb96 feat(rust): support RcWeak/ArcWeak for circular reference
tracking (#2714)
29257eb96 is described below
commit 29257eb963cd8eea26b3b68a82b7fa9ad0b0bb66
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Oct 8 17:46:57 2025 +0800
feat(rust): support RcWeak/ArcWeak for circular reference tracking (#2714)
## Why?
support RcWeak/ArcWeak for circular reference tracking
## What does this PR do?
This PR support RcWeak/ArcWeak for circular reference tracking by:
- introduce `RcWeak/ArcWeak` wrapper, whose reference is mutable, so
that for deserialization, if the parent object hasn't finished the
deserialization, we can use `RcWeak/ArcWeak` as a placeholder, and
reigster a callback for later update after the parent object is finished
the serialization.
- Extend `RefReader` to add callback/reserve_ref_id for circular
reference support.
- Add serializer support for RcWeak/ArcWeak, and `RefCell/Mutex`.
The xlang support for circular reference is not supported in this PR.
Circular reference serialized by rust can only be deserialized by rust
currently. Xlang support needs extra work to skip extra written meta.
Use example:
```rust
#[derive(ForyObject, Debug)]
struct Node {
value: i32,
// Weak ref to parent Rc<RefCell<Node>>
parent: RcWeak<RefCell<Node>>,
// Strong refs to children Rc<RefCell<Node>>
children: Vec<Rc<RefCell<Node>>>,
}
#[test]
fn test_node_circular_reference_with_parent_children() {
// Register the Node type with Fory
let mut fury = Fory::default();
fury.register::<Node>(2000);
// Create parent
let parent = Rc::new(RefCell::new(Node {
value: 1,
parent: RcWeak::new(),
children: vec![],
}));
// Create children pointing back to parent via weak ref
let child1 = Rc::new(RefCell::new(Node {
value: 2,
parent: RcWeak::new(),
children: vec![],
}));
let child2 = Rc::new(RefCell::new(Node {
value: 3,
parent: RcWeak::new(),
children: vec![],
}));
// Add children to parent's children list
parent.borrow_mut().children.push(child1.clone());
parent.borrow_mut().children.push(child2.clone());
// Set children's parent weak refs to point back to parent (creating
circular reference)
child1.borrow_mut().parent = RcWeak::from(&parent);
child2.borrow_mut().parent = RcWeak::from(&parent);
// --- Serialize the parent node (will include children recursively) ---
let serialized = fury.serialize(&parent);
// --- Deserialize ---
let deserialized: Rc<RefCell<Node>> =
fury.deserialize(&serialized).unwrap();
// --- Verify ---
let des_parent = deserialized.borrow();
assert_eq!(des_parent.value, 1);
assert_eq!(des_parent.children.len(), 2);
for child in &des_parent.children {
let upgraded_parent = child.borrow().parent.upgrade();
assert!(upgraded_parent.is_some());
assert!(Rc::ptr_eq(&deserialized, &upgraded_parent.unwrap()));
}
}
#[test]
fn test_arc_mutex_circular_reference() {
#[derive(ForyObject)]
struct Node {
val: i32,
parent: ArcWeak<Mutex<Node>>,
children: Vec<Arc<Mutex<Node>>>,
}
let mut fury = Fory::default();
fury.register::<Node>(6000);
let parent = Arc::new(Mutex::new(Node {
val: 10,
parent: ArcWeak::new(),
children: vec![],
}));
let child1 = Arc::new(Mutex::new(Node {
val: 20,
parent: ArcWeak::from(&parent),
children: vec![],
}));
let child2 = Arc::new(Mutex::new(Node {
val: 30,
parent: ArcWeak::from(&parent),
children: vec![],
}));
parent.lock().unwrap().children.push(child1.clone());
parent.lock().unwrap().children.push(child2.clone());
let serialized = fury.serialize(&parent);
let deserialized: Arc<Mutex<Node>> =
fury.deserialize(&serialized).unwrap();
assert_eq!(deserialized.lock().unwrap().children.len(), 2);
for child in &deserialized.lock().unwrap().children {
let upgraded_parent =
child.lock().unwrap().parent.upgrade().unwrap();
assert!(Arc::ptr_eq(&deserialized, &upgraded_parent));
}
}
```
## Related issues
Closes #2679
## 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
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
Delete section if not applicable.
-->
---
AGENTS.md | 3 +
CONTRIBUTING.md | 2 +
docs/guide/DEVELOPMENT.md | 2 +
rust/fory-core/src/fory.rs | 1 +
rust/fory-core/src/lib.rs | 2 +-
rust/fory-core/src/resolver/ref_resolver.rs | 67 +++-
rust/fory-core/src/serializer/arc.rs | 46 ++-
rust/fory-core/src/serializer/collection.rs | 9 +-
rust/fory-core/src/serializer/map.rs | 4 +-
rust/fory-core/src/serializer/mod.rs | 10 +
rust/fory-core/src/serializer/mutex.rs | 110 ++++++
rust/fory-core/src/serializer/rc.rs | 46 ++-
rust/fory-core/src/serializer/refcell.rs | 98 ++++++
rust/fory-core/src/serializer/weak.rs | 502 ++++++++++++++++++++++++++++
rust/fory-core/src/types.rs | 2 +-
rust/fory-derive/src/object/misc.rs | 8 +-
rust/fory-derive/src/object/read.rs | 72 ++--
rust/fory-derive/src/object/serializer.rs | 19 +-
rust/fory-derive/src/object/util.rs | 138 +++++++-
rust/fory-derive/src/object/write.rs | 43 ++-
rust/fory-derive/src/util.rs | 8 +-
rust/fory/src/lib.rs | 6 +-
rust/tests/tests/test_mutex.rs | 51 +++
rust/tests/tests/test_ref_resolver.rs | 61 ++++
rust/tests/tests/test_refcell.rs | 60 ++++
rust/tests/tests/test_weak.rs | 253 ++++++++++++++
26 files changed, 1513 insertions(+), 110 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 46a8d951f..882f20c92 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -137,6 +137,9 @@ cargo clippy --all-targets --all-features -- -D warnings
# Run tests (requires test features)
cargo test --features tests
+# run specific test
+cargo test -p fory-tests --test $test_file $test_method
+
# Format code
cargo fmt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 99688f70b..6e8461076 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -62,6 +62,8 @@ go test -v fory_xlang_test.go
```bash
cd rust
cargo test
+# run test with specific test file and method
+cargo test -p fory-tests --test $test_file $test_method
```
### JavaScript
diff --git a/docs/guide/DEVELOPMENT.md b/docs/guide/DEVELOPMENT.md
index 54c0b7296..b3ac9d360 100644
--- a/docs/guide/DEVELOPMENT.md
+++ b/docs/guide/DEVELOPMENT.md
@@ -96,6 +96,8 @@ cd rust
cargo build
# run test
cargo test
+# run specific test
+cargo test -p fory-tests --test $test_file $test_method
```
#### Environment Requirements
diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs
index a94d5810a..c7bf9dca8 100644
--- a/rust/fory-core/src/fory.rs
+++ b/rust/fory-core/src/fory.rs
@@ -184,6 +184,7 @@ impl Fory {
if bytes_to_skip > 0 {
context.reader.skip(bytes_to_skip as u32);
}
+ context.ref_reader.resolve_callbacks();
result
}
diff --git a/rust/fory-core/src/lib.rs b/rust/fory-core/src/lib.rs
index 1ada7cdd7..d3049f604 100644
--- a/rust/fory-core/src/lib.rs
+++ b/rust/fory-core/src/lib.rs
@@ -148,4 +148,4 @@ pub mod util;
// Re-export paste for use in macros
pub use paste;
-// Trait object macros are available at crate root due to #[macro_export]
+pub use crate::serializer::weak::{ArcWeak, RcWeak};
diff --git a/rust/fory-core/src/resolver/ref_resolver.rs
b/rust/fory-core/src/resolver/ref_resolver.rs
index 5c1c87e74..215b10832 100644
--- a/rust/fory-core/src/resolver/ref_resolver.rs
+++ b/rust/fory-core/src/resolver/ref_resolver.rs
@@ -55,13 +55,15 @@ pub struct RefWriter {
next_ref_id: u32,
}
+type UpdateCallback = Box<dyn FnOnce(&RefReader)>;
+
impl RefWriter {
/// Creates a new RefWriter instance.
pub fn new() -> Self {
Self::default()
}
- /// Attempt to write a reference for an Rc<T>.
+ /// Attempt to write a reference for an `Rc<T>`.
///
/// Returns true if a reference was written (indicating this object has
been
/// seen before), false if this is the first occurrence and the object
should
@@ -80,12 +82,10 @@ impl RefWriter {
let ptr_addr = Rc::as_ptr(rc) as *const () as usize;
if let Some(&ref_id) = self.refs.get(&ptr_addr) {
- // This object has been seen before, write a reference
writer.write_i8(RefFlag::Ref as i8);
writer.write_u32(ref_id);
true
} else {
- // First time seeing this object, register it and return false
let ref_id = self.next_ref_id;
self.next_ref_id += 1;
self.refs.insert(ptr_addr, ref_id);
@@ -94,7 +94,7 @@ impl RefWriter {
}
}
- /// Attempt to write a reference for an Arc<T>.
+ /// Attempt to write a reference for an `Arc<T>`.
///
/// Returns true if a reference was written (indicating this object has
been
/// seen before), false if this is the first occurrence and the object
should
@@ -163,6 +163,8 @@ impl RefWriter {
pub struct RefReader {
/// Vector to store boxed objects for reference resolution
refs: Vec<Box<dyn Any>>,
+ /// Callbacks to execute when references are resolved
+ callbacks: Vec<UpdateCallback>,
}
impl RefReader {
@@ -171,7 +173,26 @@ impl RefReader {
Self::default()
}
- /// Store an Rc<T> for later reference resolution during deserialization.
+ /// Reserve a reference ID slot without storing anything yet.
+ ///
+ /// Returns the reserved reference ID that will be used when storing the
object later.
+ pub fn reserve_ref_id(&mut self) -> u32 {
+ let ref_id = self.refs.len() as u32;
+ self.refs.push(Box::new(()));
+ ref_id
+ }
+
+ /// Store an `Rc<T>` at a previously reserved reference ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `ref_id` - The reference ID that was reserved
+ /// * `rc` - The Rc to store
+ pub fn store_rc_ref_at<T: 'static + ?Sized>(&mut self, ref_id: u32, rc:
Rc<T>) {
+ self.refs[ref_id as usize] = Box::new(rc);
+ }
+
+ /// Store an `Rc<T>` for later reference resolution during deserialization.
///
/// # Arguments
///
@@ -186,7 +207,17 @@ impl RefReader {
ref_id
}
- /// Store an Arc<T> for later reference resolution during deserialization.
+ /// Store an `Arc<T>` at a previously reserved reference ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `ref_id` - The reference ID that was reserved
+ /// * `arc` - The Arc to store
+ pub fn store_arc_ref_at<T: 'static + ?Sized>(&mut self, ref_id: u32, arc:
Arc<T>) {
+ self.refs[ref_id as usize] = Box::new(arc);
+ }
+
+ /// Store an `Arc<T>` for later reference resolution during
deserialization.
///
/// # Arguments
///
@@ -231,6 +262,15 @@ impl RefReader {
any_box.downcast_ref::<Arc<T>>().cloned()
}
+ /// Add a callback to be executed when weak references are resolved.
+ ///
+ /// # Arguments
+ ///
+ /// * `callback` - A closure that takes a reference to the RefReader
+ pub fn add_callback(&mut self, callback: UpdateCallback) {
+ self.callbacks.push(callback);
+ }
+
/// Read a reference flag and determine what action to take.
///
/// # Arguments
@@ -268,10 +308,23 @@ impl RefReader {
reader.read_u32()
}
- /// Clear all stored references.
+ /// Execute all pending callbacks to resolve weak pointer references.
+ ///
+ /// This should be called after deserialization completes to update any
weak pointers
+ /// that referenced objects which were not yet available during
deserialization.
+ pub fn resolve_callbacks(&mut self) {
+ let callbacks = std::mem::take(&mut self.callbacks);
+ for callback in callbacks {
+ callback(self);
+ }
+ }
+
+ /// Clear all stored references and callbacks.
///
/// This is useful for reusing the RefReader for multiple deserialization
operations.
pub fn clear(&mut self) {
+ self.resolve_callbacks();
self.refs.clear();
+ self.callbacks.clear();
}
}
diff --git a/rust/fory-core/src/serializer/arc.rs
b/rust/fory-core/src/serializer/arc.rs
index af2b13cf7..f62b2e119 100644
--- a/rust/fory-core/src/serializer/arc.rs
+++ b/rust/fory-core/src/serializer/arc.rs
@@ -24,7 +24,28 @@ use anyhow::anyhow;
use std::sync::Arc;
impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for
Arc<T> {
- fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ fn fory_is_shared_ref() -> bool {
+ true
+ }
+
+ fn fory_write(&self, context: &mut WriteContext, is_field: bool) {
+ if !context.ref_writer.try_write_arc_ref(context.writer, self) {
+ T::fory_write_data(self.as_ref(), context, is_field);
+ }
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ // When Arc is nested inside another shared ref (like Rc<Arc<T>>),
+ // the outer ref calls fory_write_data on the inner Arc.
+ // We still need to track the Arc's own references here.
+ self.fory_write(context, is_field);
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_read(context: &mut ReadContext, is_field: bool) -> Result<Self,
Error> {
let ref_flag = context.ref_reader.read_ref_flag(&mut context.reader);
match ref_flag {
@@ -41,30 +62,29 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static>
Serializer for Arc<T>
Ok(Arc::new(inner))
}
RefFlag::RefValue => {
+ let ref_id = context.ref_reader.reserve_ref_id();
let inner = T::fory_read_data(context, is_field)?;
let arc = Arc::new(inner);
- context.ref_reader.store_arc_ref(arc.clone());
+ context.ref_reader.store_arc_ref_at(ref_id, arc.clone());
Ok(arc)
}
}
}
- fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
- T::fory_read_type_info(context, is_field);
- }
-
- fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
- if !context.ref_writer.try_write_arc_ref(context.writer, self) {
- T::fory_write_data(self.as_ref(), context, is_field);
- }
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ // When Arc is nested inside another shared ref, fory_read_data is
called.
+ // Delegate to fory_read which handles ref tracking properly.
+ Self::fory_read(context, is_field)
}
- fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
- T::fory_write_type_info(context, is_field);
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
}
fn fory_reserved_space() -> usize {
- T::fory_reserved_space()
+ // Arc is a shared ref, so we just need space for the ref tracking
+ // We don't recursively compute inner type's space to avoid infinite
recursion
+ 4
}
fn fory_get_type_id(fory: &Fory) -> u32 {
diff --git a/rust/fory-core/src/serializer/collection.rs
b/rust/fory-core/src/serializer/collection.rs
index 979a8a281..b9fdda304 100644
--- a/rust/fory-core/src/serializer/collection.rs
+++ b/rust/fory-core/src/serializer/collection.rs
@@ -76,14 +76,15 @@ pub fn write_collection<'a, T: Serializer + 'a, I:
IntoIterator<Item = &'a T>>(
context.writer.write_u8(header);
T::fory_write_type_info(context, is_field);
// context.writer.reserve((T::reserved_space() + SIZE_OF_REF_AND_TYPE) *
len);
- if T::fory_is_polymorphic() {
+ if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
+ // TOTO: make it xlang compatible
for item in &items {
item.fory_write(context, is_field);
}
} else {
+ // let skip_ref_flag =
crate::serializer::get_skip_ref_flag::<T>(context.get_fory());
+ let skip_ref_flag = is_same_type && !has_null;
for item in &items {
- // let skip_ref_flag =
crate::serializer::get_skip_ref_flag::<T>(context.get_fory());
- let skip_ref_flag = is_same_type && !has_null;
crate::serializer::write_ref_info_data(*item, context, is_field,
skip_ref_flag, true);
}
}
@@ -119,7 +120,7 @@ where
T::fory_read_type_info(context, declared);
let has_null = (header & HAS_NULL) != 0;
let is_same_type = (header & IS_SAME_TYPE) != 0;
- if T::fory_is_polymorphic() {
+ if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
(0..len)
.map(|_| T::fory_read(context, declared))
.collect::<Result<C, Error>>()
diff --git a/rust/fory-core/src/serializer/map.rs
b/rust/fory-core/src/serializer/map.rs
index a278af5c2..36e096173 100644
--- a/rust/fory-core/src/serializer/map.rs
+++ b/rust/fory-core/src/serializer/map.rs
@@ -137,12 +137,12 @@ impl<K: Serializer + ForyDefault + Eq + std::hash::Hash,
V: Serializer + ForyDef
check_and_write_null(context, is_field, key, value);
continue;
}
- if K::fory_is_polymorphic() {
+ if K::fory_is_polymorphic() || K::fory_is_shared_ref() {
key.fory_write(context, is_field);
} else {
write_ref_info_data(key, context, is_field, skip_key_ref_flag,
true);
}
- if V::fory_is_polymorphic() {
+ if V::fory_is_polymorphic() || V::fory_is_shared_ref() {
value.fory_write(context, is_field);
} else {
write_ref_info_data(value, context, is_field,
skip_val_ref_flag, true);
diff --git a/rust/fory-core/src/serializer/mod.rs
b/rust/fory-core/src/serializer/mod.rs
index 8836221d4..d20359112 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -32,15 +32,18 @@ mod datetime;
pub mod enum_;
mod list;
pub mod map;
+mod mutex;
mod number;
mod option;
mod primitive_list;
mod rc;
+mod refcell;
mod set;
pub mod skip;
mod string;
pub mod struct_;
pub mod trait_object;
+pub mod weak;
pub fn write_ref_info_data<T: Serializer + 'static>(
record: &T,
@@ -167,6 +170,13 @@ pub trait Serializer: 'static {
false
}
+ fn fory_is_shared_ref() -> bool
+ where
+ Self: Sized,
+ {
+ false
+ }
+
fn fory_get_type_id(fory: &Fory) -> u32
where
Self: Sized,
diff --git a/rust/fory-core/src/serializer/mutex.rs
b/rust/fory-core/src/serializer/mutex.rs
new file mode 100644
index 000000000..5879625b6
--- /dev/null
+++ b/rust/fory-core/src/serializer/mutex.rs
@@ -0,0 +1,110 @@
+// 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.
+
+//! Serialization support for `Mutex<T>`.
+//!
+//! This module implements [`Serializer`] and [`ForyDefault`] for
[`std::sync::Mutex<T>`].
+//! It allows thread-safe mutable containers to be part of serialized graphs.
+//!
+//! Unlike [`Rc`] and [`Arc`], `Mutex` does not do reference counting, so this
wrapper relies
+//! on the serialization of the contained `T` only.
+//!
+//! This is commonly used together with `Arc<Mutex<T>>` in threaded graph
structures.
+//!
+//! # Example
+//! ```rust
+//! use std::sync::Mutex;
+//! use fory_core::serializer::{Serializer, ForyDefault};
+//!
+//! let mutex = Mutex::new(42);
+//! // Can be serialized by the Fory framework
+//! ```
+//!
+//! # Caveats
+//!
+//! - Serialization locks the mutex while reading/writing the inner value.
+//! - If another thread holds the lock during serialization, this may block
indefinitely.
+//! You should serialize in a quiescent state with no concurrent mutation.
+//! - A poisoned mutex (from a panicked holder) will cause `.lock().unwrap()`
to panic
+//! during serialization — it is assumed this is a programmer error.
+use crate::error::Error;
+use crate::fory::Fory;
+use crate::resolver::context::{ReadContext, WriteContext};
+use crate::serializer::{ForyDefault, Serializer};
+use std::sync::Mutex;
+
+/// `Serializer` impl for `Mutex<T>`
+///
+/// Simply delegates to the serializer for `T`, allowing thread-safe interior
mutable
+/// containers to be included in serialized graphs.
+impl<T: Serializer + ForyDefault> Serializer for Mutex<T> {
+ fn fory_write(&self, context: &mut WriteContext, is_field: bool) {
+ // Don't add ref tracking for Mutex itself, just delegate to inner type
+ // The inner type will handle its own ref tracking
+ let guard = self.lock().unwrap();
+ T::fory_write(&*guard, context, is_field);
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ // When called from Rc/Arc, just delegate to inner type's data
serialization
+ let guard = self.lock().unwrap();
+ T::fory_write_data(&*guard, context, is_field);
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_reserved_space() -> usize {
+ // Mutex is transparent, delegate to inner type
+ T::fory_reserved_space()
+ }
+
+ fn fory_read(context: &mut ReadContext, is_field: bool) -> Result<Self,
Error>
+ where
+ Self: Sized + ForyDefault,
+ {
+ Ok(Mutex::new(T::fory_read(context, is_field)?))
+ }
+
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ Ok(Mutex::new(T::fory_read_data(context, is_field)?))
+ }
+
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
+ }
+
+ fn fory_get_type_id(fory: &Fory) -> u32 {
+ T::fory_get_type_id(fory)
+ }
+
+ fn fory_type_id_dyn(&self, fory: &Fory) -> u32 {
+ let guard = self.lock().unwrap();
+ (*guard).fory_type_id_dyn(fory)
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl<T: ForyDefault> ForyDefault for Mutex<T> {
+ fn fory_default() -> Self {
+ Mutex::new(T::fory_default())
+ }
+}
diff --git a/rust/fory-core/src/serializer/rc.rs
b/rust/fory-core/src/serializer/rc.rs
index 45a12ffa3..b0cafc96d 100644
--- a/rust/fory-core/src/serializer/rc.rs
+++ b/rust/fory-core/src/serializer/rc.rs
@@ -24,7 +24,28 @@ use anyhow::anyhow;
use std::rc::Rc;
impl<T: Serializer + ForyDefault + 'static> Serializer for Rc<T> {
- fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ fn fory_is_shared_ref() -> bool {
+ true
+ }
+
+ fn fory_write(&self, context: &mut WriteContext, is_field: bool) {
+ if !context.ref_writer.try_write_rc_ref(context.writer, self) {
+ T::fory_write_data(self.as_ref(), context, is_field);
+ }
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ // When Rc is nested inside another shared ref (like Arc<Rc<T>>),
+ // the outer ref calls fory_write_data on the inner Rc.
+ // We still need to track the Rc's own references here.
+ self.fory_write(context, is_field);
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_read(context: &mut ReadContext, is_field: bool) -> Result<Self,
Error> {
let ref_flag = context.ref_reader.read_ref_flag(&mut context.reader);
match ref_flag {
@@ -41,30 +62,29 @@ impl<T: Serializer + ForyDefault + 'static> Serializer for
Rc<T> {
Ok(Rc::new(inner))
}
RefFlag::RefValue => {
+ let ref_id = context.ref_reader.reserve_ref_id();
let inner = T::fory_read_data(context, is_field)?;
let rc = Rc::new(inner);
- context.ref_reader.store_rc_ref(rc.clone());
+ context.ref_reader.store_rc_ref_at(ref_id, rc.clone());
Ok(rc)
}
}
}
- fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
- T::fory_read_type_info(context, is_field);
- }
-
- fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
- if !context.ref_writer.try_write_rc_ref(context.writer, self) {
- T::fory_write_data(self.as_ref(), context, is_field);
- }
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ // When Rc is nested inside another shared ref, fory_read_data is
called.
+ // Delegate to fory_read which handles ref tracking properly.
+ Self::fory_read(context, is_field)
}
- fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
- T::fory_write_type_info(context, is_field);
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
}
fn fory_reserved_space() -> usize {
- T::fory_reserved_space()
+ // Rc is a shared ref, so we just need space for the ref tracking
+ // We don't recursively compute inner type's space to avoid infinite
recursion
+ 4
}
fn fory_get_type_id(fory: &Fory) -> u32 {
diff --git a/rust/fory-core/src/serializer/refcell.rs
b/rust/fory-core/src/serializer/refcell.rs
new file mode 100644
index 000000000..f10978c34
--- /dev/null
+++ b/rust/fory-core/src/serializer/refcell.rs
@@ -0,0 +1,98 @@
+// 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.
+
+//! Serialization support for `RefCell<T>`.
+//!
+//! This module implements `Serializer` and `ForyDefault` for
`std::cell::RefCell<T>`.
+//! It allows mutable reference containers to be part of serialized graphs,
which is often
+//! necessary when modeling object structures with interior mutability (e.g.
parent/child links).
+//!
+//! Unlike `Rc` and `Arc`, `RefCell` does not do reference counting, so this
wrapper relies
+//! on the serialization of the contained `T` only.
+//!
+//! This is commonly used together with `Rc<RefCell<T>>` in graph structures.
+//!
+//! # Example
+//! ```rust
+//! use std::cell::RefCell;
+//! let cell = RefCell::new(42);
+//! // Can be serialized by the Fory framework
+//! ```
+use crate::error::Error;
+use crate::fory::Fory;
+use crate::resolver::context::{ReadContext, WriteContext};
+use crate::serializer::{ForyDefault, Serializer};
+use std::cell::RefCell;
+
+/// `Serializer` impl for `RefCell<T>`
+///
+/// Simply delegates to the serializer for `T`, allowing interior mutable
+/// containers to be included in serialized graphs.
+impl<T: Serializer + ForyDefault> Serializer for RefCell<T> {
+ fn fory_read(context: &mut ReadContext, is_field: bool) -> Result<Self,
Error>
+ where
+ Self: Sized + ForyDefault,
+ {
+ Ok(RefCell::new(T::fory_read(context, is_field)?))
+ }
+
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ Ok(RefCell::new(T::fory_read_data(context, is_field)?))
+ }
+
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
+ }
+
+ fn fory_write(&self, context: &mut WriteContext, is_field: bool) {
+ // Don't add ref tracking for RefCell itself, just delegate to inner
type
+ // The inner type will handle its own ref tracking
+ T::fory_write(&*self.borrow(), context, is_field);
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ // When called from Rc, just delegate to inner type's data
serialization
+ T::fory_write_data(&*self.borrow(), context, is_field)
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_reserved_space() -> usize {
+ // RefCell is transparent, delegate to inner type
+ T::fory_reserved_space()
+ }
+
+ fn fory_get_type_id(fory: &Fory) -> u32 {
+ T::fory_get_type_id(fory)
+ }
+
+ fn fory_type_id_dyn(&self, fory: &Fory) -> u32 {
+ (*self.borrow()).fory_type_id_dyn(fory)
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl<T: ForyDefault> ForyDefault for RefCell<T> {
+ fn fory_default() -> Self {
+ RefCell::new(T::fory_default())
+ }
+}
diff --git a/rust/fory-core/src/serializer/weak.rs
b/rust/fory-core/src/serializer/weak.rs
new file mode 100644
index 000000000..a78915f8c
--- /dev/null
+++ b/rust/fory-core/src/serializer/weak.rs
@@ -0,0 +1,502 @@
+// 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.
+
+//! Weak pointer serialization support for `Rc` and `Arc`.
+//!
+//! This module provides [`RcWeak<T>`] and [`ArcWeak<T>`] wrapper types that
integrate
+//! Rust's `std::rc::Weak` / `std::sync::Weak` into the Fory serialization
framework,
+//! with full support for:
+//
+//! - **Reference identity tracking** — weak pointers serialize as references
to their
+//! corresponding strong pointers, ensuring shared and circular references
in the graph
+//! are preserved without duplication.
+//! - **Null weak pointers** — if the strong pointer has been dropped or was
never set,
+//! the weak will serialize as a `Null` flag.
+//! - **Forward references during deserialization** — if the strong pointer
appears later
+//! in the serialized data, the weak will be resolved after deserialization
using
+//! `RefReader` callbacks.
+//!
+//! ## When to use
+//!
+//! Use these wrappers when your graph structure contains parent/child
relationships
+//! or other shared edges where a strong pointer would cause a reference cycle.
+//! Storing a weak pointer avoids owning the target strongly, but serialization
+//! will preserve the link by reference ID.
+//!
+//! ## Example — Parent/Child Graph
+//!
+//! ```rust,ignore
+//! use fory_core::RcWeak;
+//! use std::cell::RefCell;
+//! use std::rc::Rc;
+//! use fory_derive::ForyObject;
+//!
+//! // #[derive(ForyObject)]
+//! struct Node {
+//! value: i32,
+//! parent: RcWeak<RefCell<Node>>,
+//! children: Vec<Rc<RefCell<Node>>>,
+//! }
+//!
+//! // Build a parent with two children
+//! 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![],
+//! }));
+//!
+//! let child2 = Rc::new(RefCell::new(Node {
+//! value: 3,
+//! parent: RcWeak::from(&parent),
+//! children: vec![],
+//! }));
+//!
+//! parent.borrow_mut().children.push(child1);
+//! parent.borrow_mut().children.push(child2);
+//!
+//! // Serialize & deserialize while preserving reference identity
+//! let mut fury = fory_core::fory::Fory::default();
+//! fury.register::<Node>(2000);
+//!
+//! let serialized = fury.serialize(&parent);
+//! let deserialized: Rc<RefCell<Node>> =
fury.deserialize(&serialized).unwrap();
+//!
+//! assert_eq!(deserialized.borrow().children.len(), 2);
+//! for child in &deserialized.borrow().children {
+//! let upgraded_parent = child.borrow().parent.upgrade().unwrap();
+//! assert!(Rc::ptr_eq(&deserialized, &upgraded_parent));
+//! }
+//! ```
+//!
+//! ## Example — Arc for Multi-Threaded Graphs
+//!
+//! ```rust,ignore
+//! use fory_core::serializer::weak::ArcWeak;
+//! use std::sync::{Arc, Mutex};
+//!
+//! #[derive(fory_derive::ForyObject)]
+//! struct Node {
+//! value: i32,
+//! parent: ArcWeak<Mutex<Node>>,
+//! }
+//!
+//! let parent = Arc::new(Mutex::new(Node { value: 1, parent: ArcWeak::new()
}));
+//! let child = Arc::new(Mutex::new(Node { value: 2, parent:
ArcWeak::from(&parent) }));
+//!
+//! let mut fury = fory_core::fory::Fory::default();
+//! fury.register::<Node>(2001);
+//!
+//! let serialized = fury.serialize(&child);
+//! let deserialized: Arc<Mutex<Node>> =
fury.deserialize(&serialized).unwrap();
+//! assert_eq!(deserialized.lock().unwrap().value, 2);
+//! ```
+//!
+//! ## Notes
+//!
+//! - These types share the same `UnsafeCell` across clones, so updating a
weak in one clone
+//! will update all of them.
+//! - During serialization, weak pointers **never serialize the target
object's data directly**
+//! — they only emit a reference to the already-serialized strong pointer,
or `Null`.
+//! - During deserialization, unresolved references will be patched up by
`RefReader::add_callback`
+//! once the strong pointer becomes available.
+
+use crate::error::Error;
+use crate::fory::Fory;
+use crate::resolver::context::{ReadContext, WriteContext};
+use crate::serializer::{ForyDefault, Serializer};
+use crate::types::RefFlag;
+use anyhow::anyhow;
+use std::cell::UnsafeCell;
+use std::rc::Rc;
+use std::sync::Arc;
+
+/// A serializable wrapper around `std::rc::Weak<T>`.
+///
+/// `RcWeak<T>` is designed for use in graph-like structures where nodes may
need to hold
+/// non-owning references to other nodes (e.g., parent pointers), and you
still want them
+/// to round-trip through serialization while preserving reference identity.
+///
+/// Unlike a raw `Weak<T>`, cloning `RcWeak` keeps all clones pointing to the
same
+/// internal `UnsafeCell`, so updates via deserialization callbacks affect all
copies.
+///
+/// # Example
+/// See module-level docs for a complete graph example.
+///
+/// # Null handling
+/// If the target `Rc<T>` has been dropped or never assigned, `upgrade()`
returns `None`
+/// and serialization will write a `RefFlag::Null` instead of a reference ID.
+pub struct RcWeak<T: ?Sized> {
+ // Use Rc<UnsafeCell> so that clones share the same cell
+ inner: Rc<UnsafeCell<std::rc::Weak<T>>>,
+}
+
+impl<T: ?Sized> std::fmt::Debug for RcWeak<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("RcWeak")
+ .field("strong_count", &self.strong_count())
+ .field("weak_count", &self.weak_count())
+ .finish()
+ }
+}
+
+impl<T> RcWeak<T> {
+ pub fn new() -> Self {
+ RcWeak {
+ inner: Rc::new(UnsafeCell::new(std::rc::Weak::new())),
+ }
+ }
+}
+
+impl<T: ?Sized> RcWeak<T> {
+ pub fn upgrade(&self) -> Option<Rc<T>> {
+ unsafe { (*self.inner.get()).upgrade() }
+ }
+
+ pub fn strong_count(&self) -> usize {
+ unsafe { (*self.inner.get()).strong_count() }
+ }
+
+ pub fn weak_count(&self) -> usize {
+ unsafe { (*self.inner.get()).weak_count() }
+ }
+
+ pub fn ptr_eq(&self, other: &Self) -> bool {
+ unsafe { std::rc::Weak::ptr_eq(&*self.inner.get(),
&*other.inner.get()) }
+ }
+
+ pub fn update(&self, weak: std::rc::Weak<T>) {
+ unsafe {
+ *self.inner.get() = weak;
+ }
+ }
+
+ pub fn from_std(weak: std::rc::Weak<T>) -> Self {
+ RcWeak {
+ inner: Rc::new(UnsafeCell::new(weak)),
+ }
+ }
+}
+
+impl<T> Default for RcWeak<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T: ?Sized> Clone for RcWeak<T> {
+ fn clone(&self) -> Self {
+ // Clone the Rc, not the inner Weak - this way clones share the same
cell!
+ RcWeak {
+ inner: self.inner.clone(),
+ }
+ }
+}
+
+impl<T: ?Sized> From<&Rc<T>> for RcWeak<T> {
+ fn from(rc: &Rc<T>) -> Self {
+ RcWeak::from_std(Rc::downgrade(rc))
+ }
+}
+
+unsafe impl<T: ?Sized> Send for RcWeak<T> where std::rc::Weak<T>: Send {}
+unsafe impl<T: ?Sized> Sync for RcWeak<T> where std::rc::Weak<T>: Sync {}
+
+/// A serializable wrapper around `std::sync::Weak<T>` (thread-safe).
+///
+/// `ArcWeak<T>` works exactly like [`RcWeak<T>`] but is intended for use with
+/// multi-threaded shared graphs where strong pointers are `Arc<T>`.
+///
+/// All clones of an `ArcWeak<T>` share the same `UnsafeCell` so
deserialization
+/// updates propagate to all copies.
+///
+/// # Example
+/// See module-level docs for an `Arc<Mutex<Node>>` usage example.
+pub struct ArcWeak<T: ?Sized> {
+ // Use Arc<UnsafeCell> so that clones share the same cell
+ inner: Arc<UnsafeCell<std::sync::Weak<T>>>,
+}
+
+impl<T: ?Sized> std::fmt::Debug for ArcWeak<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ArcWeak")
+ .field("strong_count", &self.strong_count())
+ .field("weak_count", &self.weak_count())
+ .finish()
+ }
+}
+
+impl<T> ArcWeak<T> {
+ pub fn new() -> Self {
+ ArcWeak {
+ inner: Arc::new(UnsafeCell::new(std::sync::Weak::new())),
+ }
+ }
+}
+
+impl<T: ?Sized> ArcWeak<T> {
+ pub fn upgrade(&self) -> Option<Arc<T>> {
+ unsafe { (*self.inner.get()).upgrade() }
+ }
+
+ pub fn strong_count(&self) -> usize {
+ unsafe { (*self.inner.get()).strong_count() }
+ }
+
+ pub fn weak_count(&self) -> usize {
+ unsafe { (*self.inner.get()).weak_count() }
+ }
+
+ pub fn ptr_eq(&self, other: &Self) -> bool {
+ unsafe { std::sync::Weak::ptr_eq(&*self.inner.get(),
&*other.inner.get()) }
+ }
+
+ pub fn update(&self, weak: std::sync::Weak<T>) {
+ unsafe {
+ *self.inner.get() = weak;
+ }
+ }
+
+ pub fn from_std(weak: std::sync::Weak<T>) -> Self {
+ ArcWeak {
+ inner: Arc::new(UnsafeCell::new(weak)),
+ }
+ }
+}
+
+impl<T> Default for ArcWeak<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T: ?Sized> Clone for ArcWeak<T> {
+ fn clone(&self) -> Self {
+ // Clone the Arc, not the inner Weak - this way clones share the same
cell!
+ ArcWeak {
+ inner: self.inner.clone(),
+ }
+ }
+}
+
+impl<T: ?Sized> From<&Arc<T>> for ArcWeak<T> {
+ fn from(arc: &Arc<T>) -> Self {
+ ArcWeak::from_std(Arc::downgrade(arc))
+ }
+}
+
+unsafe impl<T: ?Sized + Send + Sync> Send for ArcWeak<T> {}
+unsafe impl<T: ?Sized + Send + Sync> Sync for ArcWeak<T> {}
+
+impl<T: Serializer + ForyDefault + 'static> Serializer for RcWeak<T> {
+ fn fory_is_shared_ref() -> bool {
+ true
+ }
+
+ fn fory_write(&self, context: &mut WriteContext, _is_field: bool) {
+ if let Some(rc) = self.upgrade() {
+ if context.ref_writer.try_write_rc_ref(context.writer, &rc) {
+ return;
+ }
+ T::fory_write_data(&*rc, context, _is_field);
+ } else {
+ context.writer.write_i8(RefFlag::Null as i8);
+ }
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ self.fory_write(context, is_field);
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_read(context: &mut ReadContext, _is_field: bool) -> Result<Self,
Error> {
+ let ref_flag = context.ref_reader.read_ref_flag(&mut context.reader);
+
+ match ref_flag {
+ RefFlag::Null => Ok(RcWeak::new()),
+ RefFlag::RefValue => {
+ let data = T::fory_read_data(context, _is_field)?;
+ let rc = Rc::new(data);
+ let ref_id = context.ref_reader.store_rc_ref(rc);
+ let rc = context.ref_reader.get_rc_ref::<T>(ref_id).unwrap();
+ Ok(RcWeak::from(&rc))
+ }
+ RefFlag::Ref => {
+ let ref_id = context.ref_reader.read_ref_id(&mut
context.reader);
+
+ if let Some(rc) = context.ref_reader.get_rc_ref::<T>(ref_id) {
+ Ok(RcWeak::from(&rc))
+ } else {
+ let result_weak = RcWeak::new();
+ let callback_weak = result_weak.clone();
+
+ context.ref_reader.add_callback(Box::new(move |ref_reader|
{
+ if let Some(rc) = ref_reader.get_rc_ref::<T>(ref_id) {
+ callback_weak.update(Rc::downgrade(&rc));
+ }
+ }));
+
+ Ok(result_weak)
+ }
+ }
+ _ => Err(anyhow!("Weak can only be Null, RefValue or Ref, got
{:?}", ref_flag).into()),
+ }
+ }
+
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ Self::fory_read(context, is_field)
+ }
+
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
+ }
+
+ fn fory_reserved_space() -> usize {
+ // RcWeak is a shared ref, return a const to avoid infinite recursion
+ 4
+ }
+
+ fn fory_get_type_id(fory: &Fory) -> u32 {
+ T::fory_get_type_id(fory)
+ }
+
+ fn fory_type_id_dyn(&self, fory: &Fory) -> u32 {
+ if let Some(rc) = self.upgrade() {
+ (*rc).fory_type_id_dyn(fory)
+ } else {
+ T::fory_get_type_id(fory)
+ }
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl<T: ForyDefault> ForyDefault for RcWeak<T> {
+ fn fory_default() -> Self {
+ RcWeak::new()
+ }
+}
+
+impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for
ArcWeak<T> {
+ fn fory_is_shared_ref() -> bool {
+ true
+ }
+
+ fn fory_write(&self, context: &mut WriteContext, _is_field: bool) {
+ if let Some(arc) = self.upgrade() {
+ // IMPORTANT: If the target Arc was serialized already, just write
a ref
+ if context.ref_writer.try_write_arc_ref(context.writer, &arc) {
+ // Already seen, wrote Ref flag + id, we're done
+ return;
+ }
+ // First time seeing this object, write RefValue and then its data
+ T::fory_write_data(&*arc, context, _is_field);
+ } else {
+ context.writer.write_i8(RefFlag::Null as i8);
+ }
+ }
+
+ fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
+ self.fory_write(context, is_field);
+ }
+
+ fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
+ T::fory_write_type_info(context, is_field);
+ }
+
+ fn fory_read(context: &mut ReadContext, _is_field: bool) -> Result<Self,
Error> {
+ let ref_flag = context.ref_reader.read_ref_flag(&mut context.reader);
+
+ match ref_flag {
+ RefFlag::Null => Ok(ArcWeak::new()),
+ RefFlag::RefValue => {
+ let data = T::fory_read_data(context, _is_field)?;
+ let arc = Arc::new(data);
+ let ref_id = context.ref_reader.store_arc_ref(arc);
+ let arc = context.ref_reader.get_arc_ref::<T>(ref_id).unwrap();
+ let weak = ArcWeak::from(&arc);
+ Ok(weak)
+ }
+ RefFlag::Ref => {
+ let ref_id = context.ref_reader.read_ref_id(&mut
context.reader);
+ let weak = ArcWeak::new();
+
+ if let Some(arc) = context.ref_reader.get_arc_ref::<T>(ref_id)
{
+ weak.update(Arc::downgrade(&arc));
+ } else {
+ // Capture the raw pointer to the UnsafeCell so we can
update it in the callback
+ let weak_ptr = weak.inner.get();
+ context.ref_reader.add_callback(Box::new(move |ref_reader|
{
+ if let Some(arc) = ref_reader.get_arc_ref::<T>(ref_id)
{
+ unsafe {
+ *weak_ptr = Arc::downgrade(&arc);
+ }
+ }
+ }));
+ }
+
+ Ok(weak)
+ }
+ _ => Err(anyhow!("Weak can only be Null, RefValue or Ref, got
{:?}", ref_flag).into()),
+ }
+ }
+
+ fn fory_read_data(context: &mut ReadContext, is_field: bool) ->
Result<Self, Error> {
+ Self::fory_read(context, is_field)
+ }
+
+ fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
+ T::fory_read_type_info(context, is_field);
+ }
+
+ fn fory_reserved_space() -> usize {
+ // ArcWeak is a shared ref, return a const to avoid infinite recursion
+ 4
+ }
+
+ fn fory_get_type_id(fory: &Fory) -> u32 {
+ T::fory_get_type_id(fory)
+ }
+
+ fn fory_type_id_dyn(&self, fory: &Fory) -> u32 {
+ if let Some(arc) = self.upgrade() {
+ (*arc).fory_type_id_dyn(fory)
+ } else {
+ T::fory_get_type_id(fory)
+ }
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+impl<T: ForyDefault> ForyDefault for ArcWeak<T> {
+ fn fory_default() -> Self {
+ ArcWeak::new()
+ }
+}
diff --git a/rust/fory-core/src/types.rs b/rust/fory-core/src/types.rs
index 5ec2d5403..d89d25745 100644
--- a/rust/fory-core/src/types.rs
+++ b/rust/fory-core/src/types.rs
@@ -26,7 +26,7 @@ pub enum StringFlag {
UTF8 = 1,
}
-#[derive(TryFromPrimitive)]
+#[derive(Debug, TryFromPrimitive)]
#[repr(i8)]
pub enum RefFlag {
Null = -3,
diff --git a/rust/fory-derive/src/object/misc.rs
b/rust/fory-derive/src/object/misc.rs
index 28ba6d2b5..ab519ec0f 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -22,7 +22,7 @@ use syn::Field;
use super::util::{
classify_trait_object_field, generic_tree_to_tokens, get_sort_fields_ts,
parse_generic_tree,
- TraitObjectField,
+ StructField,
};
// Global type ID counter that auto-grows from 0 at macro processing time
@@ -84,14 +84,14 @@ pub fn gen_type_def(fields: &[&Field]) -> TokenStream {
let ty = &field.ty;
let name = format!("{}", field.ident.as_ref().expect("should be field
name"));
match classify_trait_object_field(ty) {
- TraitObjectField::None => {
+ StructField::None => {
let generic_tree = parse_generic_tree(ty);
let generic_token = generic_tree_to_tokens(&generic_tree,
false);
quote! {
fory_core::meta::FieldInfo::new(#name, #generic_token)
}
}
- TraitObjectField::VecRc(_) | TraitObjectField::VecArc(_) => {
+ StructField::VecRc(_) | StructField::VecArc(_) => {
quote! {
fory_core::meta::FieldInfo::new(#name,
fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::LIST as u32,
@@ -99,7 +99,7 @@ pub fn gen_type_def(fields: &[&Field]) -> TokenStream {
})
}
}
- TraitObjectField::HashMapRc(..) | TraitObjectField::HashMapArc(..)
=> {
+ StructField::HashMapRc(..) | StructField::HashMapArc(..) => {
quote! {
fory_core::meta::FieldInfo::new(#name,
fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::MAP as u32,
diff --git a/rust/fory-derive/src/object/read.rs
b/rust/fory-derive/src/object/read.rs
index e31b5bdb5..d14d332da 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -21,7 +21,7 @@ use syn::{Field, Type};
use super::util::{
classify_trait_object_field, create_wrapper_types_arc,
create_wrapper_types_rc,
- generic_tree_to_tokens, parse_generic_tree, NullableTypeNode,
TraitObjectField,
+ generic_tree_to_tokens, parse_generic_tree, NullableTypeNode, StructField,
};
fn create_private_field_name(field: &Field) -> Ident {
@@ -39,9 +39,9 @@ fn declare_var(fields: &[&Field]) -> Vec<TokenStream> {
let ty = &field.ty;
let var_name = create_private_field_name(field);
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(_)
- | TraitObjectField::RcDyn(_)
- | TraitObjectField::ArcDyn(_) => {
+ StructField::BoxDyn(_)
+ | StructField::RcDyn(_)
+ | StructField::ArcDyn(_) => {
quote! {
let mut #var_name: #ty = <#ty as
fory_core::serializer::ForyDefault>::fory_default();
}
@@ -63,9 +63,7 @@ fn assign_value(fields: &[&Field]) -> Vec<TokenStream> {
let name = &field.ident;
let var_name = create_private_field_name(field);
match classify_trait_object_field(&field.ty) {
- TraitObjectField::BoxDyn(_)
- | TraitObjectField::RcDyn(_)
- | TraitObjectField::ArcDyn(_) => {
+ StructField::BoxDyn(_) | StructField::RcDyn(_) |
StructField::ArcDyn(_) => {
quote! {
#name: #var_name
}
@@ -85,7 +83,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
let name_str = field.ident.as_ref().unwrap().to_string();
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(trait_name) => {
+ StructField::BoxDyn(trait_name) => {
let from_any_fn = format_ident!("from_any_internal_{}",
trait_name);
let helper_mod = format_ident!("__fory_trait_helpers_{}",
trait_name);
quote! {
@@ -110,7 +108,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::RcDyn(trait_name) => {
+ StructField::RcDyn(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -121,7 +119,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::ArcDyn(trait_name) => {
+ StructField::ArcDyn(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -132,7 +130,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::VecRc(trait_name) => {
+ StructField::VecRc(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -145,7 +143,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::VecArc(trait_name) => {
+ StructField::VecArc(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -158,7 +156,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::HashMapRc(key_ty, trait_name) => {
+ StructField::HashMapRc(key_ty, trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -171,7 +169,7 @@ fn gen_read_match_arm(field: &Field, private_ident: &Ident)
-> TokenStream {
}
}
}
- TraitObjectField::HashMapArc(key_ty, trait_name) => {
+ StructField::HashMapArc(key_ty, trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -184,6 +182,13 @@ fn gen_read_match_arm(field: &Field, private_ident:
&Ident) -> TokenStream {
}
}
}
+ StructField::Forward => {
+ quote! {
+ #name_str => {
+ #private_ident =
Some(fory_core::serializer::Serializer::fory_read(context, true)?);
+ }
+ }
+ }
_ => {
quote! {
#name_str => {
@@ -216,9 +221,9 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
.map(|(field, private_ident)| {
let ty = &field.ty;
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(_)
- | TraitObjectField::RcDyn(_)
- | TraitObjectField::ArcDyn(_) => {
+ StructField::BoxDyn(_)
+ | StructField::RcDyn(_)
+ | StructField::ArcDyn(_) => {
quote! {
let mut #private_ident: #ty = <#ty as
fory_core::serializer::ForyDefault>::fory_default();
}
@@ -252,9 +257,7 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
let original_ident = &field.ident;
let ty = &field.ty;
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(_)
- | TraitObjectField::RcDyn(_)
- | TraitObjectField::ArcDyn(_) => {
+ StructField::BoxDyn(_) | StructField::RcDyn(_) |
StructField::ArcDyn(_) => {
quote! {
#original_ident: #private_ident
}
@@ -279,7 +282,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
let field_name_str = field.ident.as_ref().unwrap().to_string();
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(trait_name) => {
+ StructField::BoxDyn(trait_name) => {
let from_any_fn = format_ident!("from_any_internal_{}",
trait_name);
let helper_mod = format_ident!("__fory_trait_helpers_{}",
trait_name);
quote! {
@@ -300,7 +303,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::RcDyn(trait_name) => {
+ StructField::RcDyn(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -311,7 +314,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::ArcDyn(trait_name) => {
+ StructField::ArcDyn(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -322,7 +325,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::VecRc(trait_name) => {
+ StructField::VecRc(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -335,7 +338,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::VecArc(trait_name) => {
+ StructField::VecArc(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -348,7 +351,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::HashMapRc(key_ty, trait_name) => {
+ StructField::HashMapRc(key_ty, trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -361,7 +364,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::HashMapArc(key_ty, trait_name) => {
+ StructField::HashMapArc(key_ty, trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -374,7 +377,7 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::ContainsTraitObject => {
+ StructField::ContainsTraitObject => {
quote! {
if _field.field_name.as_str() == #field_name_str {
let skip_ref_flag =
fory_core::serializer::get_skip_ref_flag::<#ty>(context.get_fory());
@@ -382,7 +385,14 @@ fn gen_read_compatible_match_arm(field: &Field, var_name:
&Ident) -> TokenStream
}
}
}
- TraitObjectField::None => {
+ StructField::Forward => {
+ quote! {
+ if _field.field_name.as_str() == #field_name_str {
+ #var_name =
Some(fory_core::serializer::Serializer::fory_read(context, true).unwrap());
+ }
+ }
+ }
+ StructField::None => {
let generic_tree = parse_generic_tree(ty);
let generic_token = generic_tree_to_tokens(&generic_tree, true);
let read_nullable_fn_name = create_read_nullable_fn_name(field);
@@ -451,7 +461,7 @@ pub fn gen_read(struct_ident: &Ident) -> TokenStream {
}
}
-pub fn gen_read_compatible(fields: &[&Field], _struct_ident: &Ident) ->
TokenStream {
+pub fn gen_read_compatible(fields: &[&Field]) -> TokenStream {
let pattern_items = fields.iter().map(|field| {
let var_name = create_private_field_name(field);
gen_read_compatible_match_arm(field, &var_name)
@@ -487,7 +497,7 @@ pub fn gen_read_nullable(fields: &[&Field]) -> TokenStream {
.filter_map(|field| {
let ty = &field.ty;
match classify_trait_object_field(ty) {
- TraitObjectField::None => {
+ StructField::None => {
let fn_name = create_read_nullable_fn_name(field);
let generic_tree = parse_generic_tree(ty);
let nullable_generic_tree =
NullableTypeNode::from(generic_tree);
diff --git a/rust/fory-derive/src/object/serializer.rs
b/rust/fory-derive/src/object/serializer.rs
index ca75547de..fc76ba9be 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -38,6 +38,8 @@ fn has_existing_default(ast: &syn::DeriveInput, trait_name:
&str) -> bool {
pub fn derive_serializer(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
+ use crate::object::util::{clear_struct_context, set_struct_context};
+ set_struct_context(&name.to_string());
// Check if ForyDefault is already derived/implemented
let has_existing_default = has_existing_default(ast, "ForyDefault");
@@ -56,7 +58,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput) ->
TokenStream {
misc::gen_actual_type_id(),
misc::gen_get_sorted_field_names(&fields),
misc::gen_type_def(&fields),
- read::gen_read_compatible(&fields, name),
+ read::gen_read_compatible(&fields),
)
}
syn::Data::Enum(s) => (
@@ -190,7 +192,9 @@ pub fn derive_serializer(ast: &syn::DeriveInput) ->
TokenStream {
#deserialize_nullable_ts
}
};
- gen.into()
+ let code = gen.into();
+ clear_struct_context();
+ code
}
fn generate_default_impl(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
@@ -203,7 +207,7 @@ fn generate_default_impl(ast: &syn::DeriveInput) ->
proc_macro2::TokenStream {
use super::util::{
classify_trait_object_field, create_wrapper_types_arc,
create_wrapper_types_rc,
- TraitObjectField,
+ StructField,
};
let field_inits = fields.iter().map(|field| {
@@ -211,7 +215,7 @@ fn generate_default_impl(ast: &syn::DeriveInput) ->
proc_macro2::TokenStream {
let ty = &field.ty;
match classify_trait_object_field(ty) {
- TraitObjectField::RcDyn(trait_name) => {
+ StructField::RcDyn(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -222,7 +226,7 @@ fn generate_default_impl(ast: &syn::DeriveInput) ->
proc_macro2::TokenStream {
}
}
}
- TraitObjectField::ArcDyn(trait_name) => {
+ StructField::ArcDyn(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -233,6 +237,11 @@ fn generate_default_impl(ast: &syn::DeriveInput) ->
proc_macro2::TokenStream {
}
}
}
+ StructField::Forward => {
+ quote! {
+ #ident: <#ty as
fory_core::serializer::ForyDefault>::fory_default()
+ }
+ }
_ => {
quote! {
#ident: <#ty as
fory_core::serializer::ForyDefault>::fory_default()
diff --git a/rust/fory-derive/src/object/util.rs
b/rust/fory-derive/src/object/util.rs
index 3698b5e74..0330ab8df 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -22,9 +22,36 @@ use crate::util::{
use fory_core::types::{TypeId, BASIC_TYPE_NAMES, CONTAINER_TYPE_NAMES,
PRIMITIVE_ARRAY_TYPE_MAP};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
+use std::cell::RefCell;
use std::fmt;
use syn::{parse_str, Field, GenericArgument, PathArguments, Type};
+thread_local! {
+ static MACRO_CONTEXT: RefCell<Option<MacroContext>> = const
{RefCell::new(None)};
+}
+
+struct MacroContext {
+ struct_name: String,
+}
+
+pub(super) fn set_struct_context(name: &str) {
+ MACRO_CONTEXT.with(|ctx| {
+ *ctx.borrow_mut() = Some(MacroContext {
+ struct_name: name.to_string(),
+ });
+ });
+}
+
+pub(super) fn clear_struct_context() {
+ MACRO_CONTEXT.with(|ctx| {
+ *ctx.borrow_mut() = None;
+ });
+}
+
+fn get_struct_name() -> Option<String> {
+ MACRO_CONTEXT.with(|ctx| ctx.borrow().as_ref().map(|c|
c.struct_name.clone()))
+}
+
pub(super) fn contains_trait_object(ty: &Type) -> bool {
match ty {
Type::TraitObject(_) => true,
@@ -72,7 +99,7 @@ pub(super) fn create_wrapper_types_arc(trait_name: &str) ->
WrapperTypes {
}
}
-pub(super) enum TraitObjectField {
+pub(super) enum StructField {
BoxDyn(String),
RcDyn(String),
ArcDyn(String),
@@ -81,31 +108,106 @@ pub(super) enum TraitObjectField {
HashMapRc(Box<Type>, String),
HashMapArc(Box<Type>, String),
ContainsTraitObject,
+ Forward,
None,
}
-pub(super) fn classify_trait_object_field(ty: &Type) -> TraitObjectField {
+fn is_forward_field(ty: &Type) -> bool {
+ let struct_name = match get_struct_name() {
+ Some(name) => name,
+ None => return false,
+ };
+ is_forward_field_internal(ty, &struct_name)
+}
+
+fn is_forward_field_internal(ty: &Type, struct_name: &str) -> bool {
+ match ty {
+ Type::TraitObject(_) => true,
+
+ Type::Path(type_path) => {
+ if let Some(seg) = type_path.path.segments.last() {
+ // Direct match: type is the struct itself
+ if seg.ident == struct_name {
+ return true;
+ }
+
+ // Special cases for weak pointers
+ if seg.ident == "RcWeak" || seg.ident == "ArcWeak" {
+ return true;
+ }
+
+ // Check smart pointers: Rc<T> / Arc<T>
+ if seg.ident == "Rc" || seg.ident == "Arc" {
+ if let PathArguments::AngleBracketed(args) =
&seg.arguments {
+ if let Some(GenericArgument::Type(inner_ty)) =
args.args.first() {
+ match inner_ty {
+ // Inner type is trait object
+ Type::TraitObject(trait_obj) => {
+ if trait_obj
+ .bounds
+ .iter()
+ .any(|b|
b.to_token_stream().to_string() == "Any")
+ {
+ // Rc<dyn Any> → return true
+ return true;
+ } else {
+ // Rc<dyn SomethingElse> → return false
+ return false;
+ }
+ }
+ // Inner type is not a trait object → return
true
+ _ => {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // Recursively check other generic args
+ if let PathArguments::AngleBracketed(args) = &seg.arguments {
+ for arg in &args.args {
+ if let GenericArgument::Type(inner_ty) = arg {
+ if is_forward_field_internal(inner_ty,
struct_name) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ false
+ }
+
+ _ => false,
+ }
+}
+
+pub(super) fn classify_trait_object_field(ty: &Type) -> StructField {
+ if is_forward_field(ty) {
+ return StructField::Forward;
+ }
if let Some((_, trait_name)) = is_box_dyn_trait(ty) {
- return TraitObjectField::BoxDyn(trait_name);
+ return StructField::BoxDyn(trait_name);
}
if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
- return TraitObjectField::RcDyn(trait_name);
+ return StructField::RcDyn(trait_name);
}
if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
- return TraitObjectField::ArcDyn(trait_name);
+ return StructField::ArcDyn(trait_name);
}
if let Some(collection_info) = detect_collection_with_trait_object(ty) {
return match collection_info {
- CollectionTraitInfo::VecRc(t) => TraitObjectField::VecRc(t),
- CollectionTraitInfo::VecArc(t) => TraitObjectField::VecArc(t),
- CollectionTraitInfo::HashMapRc(k, t) =>
TraitObjectField::HashMapRc(k, t),
- CollectionTraitInfo::HashMapArc(k, t) =>
TraitObjectField::HashMapArc(k, t),
+ CollectionTraitInfo::VecRc(t) => StructField::VecRc(t),
+ CollectionTraitInfo::VecArc(t) => StructField::VecArc(t),
+ CollectionTraitInfo::HashMapRc(k, t) => StructField::HashMapRc(k,
t),
+ CollectionTraitInfo::HashMapArc(k, t) =>
StructField::HashMapArc(k, t),
};
}
if contains_trait_object(ty) {
- return TraitObjectField::ContainsTraitObject;
+ return StructField::ContainsTraitObject;
}
- TraitObjectField::None
+ StructField::None
}
#[derive(Debug)]
@@ -744,6 +846,14 @@ pub(super) fn get_sort_fields_ts(fields: &[&Field]) ->
TokenStream {
let mut map_fields = Vec::new();
let mut struct_or_enum_fields = Vec::new();
+ // First handle Forward fields separately to avoid borrow checker
issues
+ for field in fields {
+ if is_forward_field(&field.ty) {
+ let ident = field.ident.as_ref().unwrap().to_string();
+ collection_fields.push((ident, "Forward".to_string(),
TypeId::LIST as u32));
+ }
+ }
+
let mut group_field = |ident: String, ty: &str| {
if PRIMITIVE_TYPE_NAMES.contains(&ty) {
let type_id = get_primitive_type_id(ty);
@@ -777,6 +887,12 @@ pub(super) fn get_sort_fields_ts(fields: &[&Field]) ->
TokenStream {
for field in fields {
let ident = field.ident.as_ref().unwrap().to_string();
+
+ // Skip if already handled as Forward field
+ if is_forward_field(&field.ty) {
+ continue;
+ }
+
let ty: String = field
.ty
.to_token_stream()
diff --git a/rust/fory-derive/src/object/write.rs
b/rust/fory-derive/src/object/write.rs
index ef849dda3..9f8f51068 100644
--- a/rust/fory-derive/src/object/write.rs
+++ b/rust/fory-derive/src/object/write.rs
@@ -16,8 +16,7 @@
// under the License.
use super::util::{
- classify_trait_object_field, create_wrapper_types_arc,
create_wrapper_types_rc,
- TraitObjectField,
+ classify_trait_object_field, create_wrapper_types_arc,
create_wrapper_types_rc, StructField,
};
use proc_macro2::TokenStream;
use quote::quote;
@@ -27,53 +26,58 @@ pub fn gen_reserved_space(fields: &[&Field]) -> TokenStream
{
let reserved_size_expr: Vec<_> = fields.iter().map(|field| {
let ty = &field.ty;
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(_) => {
+ StructField::BoxDyn(_) => {
quote! {
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::RcDyn(trait_name) => {
+ StructField::RcDyn(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<#wrapper_ty as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::ArcDyn(trait_name) => {
+ StructField::ArcDyn(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<#wrapper_ty as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::VecRc(trait_name) => {
+ StructField::VecRc(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<Vec<#wrapper_ty> as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::VecArc(trait_name) => {
+ StructField::VecArc(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<Vec<#wrapper_ty> as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::HashMapRc(key_ty, trait_name) => {
+ StructField::HashMapRc(key_ty, trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<std::collections::HashMap<#key_ty, #wrapper_ty> as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
- TraitObjectField::HashMapArc(key_ty, trait_name) => {
+ StructField::HashMapArc(key_ty, trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
quote! {
<std::collections::HashMap<#key_ty, #wrapper_ty> as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
}
}
+ StructField::Forward => {
+ quote! {
+ <#ty as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
+ }
+ }
_ => {
quote! {
<#ty as
fory_core::serializer::Serializer>::fory_reserved_space() +
fory_core::types::SIZE_OF_REF_AND_TYPE
@@ -100,7 +104,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
let name_str = ident.as_ref().unwrap().to_string();
match classify_trait_object_field(ty) {
- TraitObjectField::BoxDyn(_) => {
+ StructField::BoxDyn(_) => {
quote! {
#name_str => {
let any_ref = self.#ident.as_any();
@@ -123,7 +127,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::RcDyn(trait_name) => {
+ StructField::RcDyn(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -134,7 +138,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::ArcDyn(trait_name) => {
+ StructField::ArcDyn(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -145,7 +149,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::VecRc(trait_name) => {
+ StructField::VecRc(trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -158,7 +162,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::VecArc(trait_name) => {
+ StructField::VecArc(trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -171,7 +175,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::HashMapRc(key_ty, trait_name) => {
+ StructField::HashMapRc(key_ty, trait_name) => {
let types = create_wrapper_types_rc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -184,7 +188,7 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
- TraitObjectField::HashMapArc(key_ty, trait_name) => {
+ StructField::HashMapArc(key_ty, trait_name) => {
let types = create_wrapper_types_arc(&trait_name);
let wrapper_ty = types.wrapper_ty;
let trait_ident = types.trait_ident;
@@ -197,6 +201,13 @@ fn gen_write_match_arm(field: &Field) -> TokenStream {
}
}
}
+ StructField::Forward => {
+ quote! {
+ #name_str => {
+
fory_core::serializer::Serializer::fory_write(&self.#ident, context, true);
+ }
+ }
+ }
_ => {
quote! {
#name_str => {
diff --git a/rust/fory-derive/src/util.rs b/rust/fory-derive/src/util.rs
index 537aa5fda..edb012171 100644
--- a/rust/fory-derive/src/util.rs
+++ b/rust/fory-derive/src/util.rs
@@ -64,6 +64,9 @@ pub fn is_rc_dyn_trait(ty: &Type) ->
Option<(&TypeTraitObject, String)> {
{
if let Some(segment) =
trait_bound.path.segments.last() {
let trait_name = segment.ident.to_string();
+ if trait_name == "Any" {
+ return None;
+ }
return Some((trait_obj, trait_name));
}
}
@@ -90,6 +93,9 @@ pub fn is_arc_dyn_trait(ty: &Type) ->
Option<(&TypeTraitObject, String)> {
{
if let Some(segment) =
trait_bound.path.segments.last() {
let trait_name = segment.ident.to_string();
+ if trait_name == "Any" {
+ return None;
+ }
return Some((trait_obj, trait_name));
}
}
@@ -110,7 +116,7 @@ pub enum CollectionTraitInfo {
// todo HashSet
}
-/// Check if a type is a collection containing Rc<dyn Trait> or Arc<dyn Trait>
+/// Check if a type is a collection containing `Rc<dyn Trait>` or `Arc<dyn
Trait>`
pub fn detect_collection_with_trait_object(ty: &Type) ->
Option<CollectionTraitInfo> {
if let Type::Path(TypePath { path, .. }) = ty {
if let Some(seg) = path.segments.last() {
diff --git a/rust/fory/src/lib.rs b/rust/fory/src/lib.rs
index ceb369d3e..ce9aea579 100644
--- a/rust/fory/src/lib.rs
+++ b/rust/fory/src/lib.rs
@@ -256,4 +256,8 @@
//! }
//! ```
-pub use fory_core::{error::Error, fory::Fory, register_trait_type,
row::from_row, row::to_row};
+pub use fory_core::{
+ error::Error, fory::Fory, register_trait_type, row::from_row, row::to_row,
types::Mode,
+ types::TypeId, ArcWeak, RcWeak,
+};
+pub use fory_derive::{ForyObject, ForyRow};
diff --git a/rust/tests/tests/test_mutex.rs b/rust/tests/tests/test_mutex.rs
new file mode 100644
index 000000000..0b22924db
--- /dev/null
+++ b/rust/tests/tests/test_mutex.rs
@@ -0,0 +1,51 @@
+// 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.
+
+use fory_core::fory::Fory;
+use std::sync::{Arc, Mutex};
+
+#[test]
+fn test_mutex_basic_serialization() {
+ let fury = Fory::default();
+ let m = Mutex::new(42i32);
+ let serialized = fury.serialize(&m);
+ let deserialized: Mutex<i32> = fury.deserialize(&serialized).unwrap();
+ assert_eq!(deserialized.lock().unwrap().clone(), 42);
+}
+
+#[test]
+fn test_arc_mutex_serialization() {
+ let fury = Fory::default();
+ let arc_mutex = Arc::new(Mutex::new(String::from("hello")));
+ let serialized = fury.serialize(&arc_mutex);
+ let deserialized: Arc<Mutex<String>> =
fury.deserialize(&serialized).unwrap();
+ assert_eq!(deserialized.lock().unwrap().as_str(), "hello");
+}
+
+#[test]
+fn test_arc_mutex_sharing_preserved() {
+ let fury = Fory::default();
+
+ let data = Arc::new(Mutex::new(123i32));
+ let list = vec![data.clone(), data.clone()];
+
+ let serialized = fury.serialize(&list);
+ let deserialized: Vec<Arc<Mutex<i32>>> =
fury.deserialize(&serialized).unwrap();
+
+ assert_eq!(deserialized.len(), 2);
+ assert!(Arc::ptr_eq(&deserialized[0], &deserialized[1]));
+}
diff --git a/rust/tests/tests/test_ref_resolver.rs
b/rust/tests/tests/test_ref_resolver.rs
index ebaaf8002..22f6f585c 100644
--- a/rust/tests/tests/test_ref_resolver.rs
+++ b/rust/tests/tests/test_ref_resolver.rs
@@ -19,6 +19,7 @@
use fory_core::buffer::Writer;
use fory_core::resolver::ref_resolver::{RefReader, RefWriter};
+use fory_core::serializer::weak::{ArcWeak, RcWeak};
use std::rc::Rc;
use std::sync::Arc;
@@ -127,3 +128,63 @@ fn test_ref_writer_ref_reader_separation() {
let retrieved = ref_reader.get_rc_ref::<i32>(ref_id).unwrap();
assert!(Rc::ptr_eq(&rc1, &retrieved));
}
+
+#[test]
+fn test_rc_weak_wrapper() {
+ let rc = Rc::new(42i32);
+ let weak = RcWeak::from(&rc);
+
+ // Test upgrade
+ assert_eq!(*weak.upgrade().unwrap(), 42);
+
+ // Test strong and weak counts
+ assert_eq!(weak.strong_count(), 1);
+ assert!(weak.weak_count() > 0);
+
+ // Test clone
+ let weak2 = weak.clone();
+ assert_eq!(*weak2.upgrade().unwrap(), 42);
+}
+
+#[test]
+fn test_arc_weak_wrapper() {
+ let arc = Arc::new(42i32);
+ let weak = ArcWeak::from(&arc);
+
+ // Test upgrade
+ assert_eq!(*weak.upgrade().unwrap(), 42);
+
+ // Test strong and weak counts
+ assert_eq!(weak.strong_count(), 1);
+ assert!(weak.weak_count() > 0);
+
+ // Test clone
+ let weak2 = weak.clone();
+ assert_eq!(*weak2.upgrade().unwrap(), 42);
+}
+
+#[test]
+fn test_rc_weak_update() {
+ let rc1 = Rc::new(10i32);
+ let rc2 = Rc::new(20i32);
+
+ let weak = RcWeak::from(&rc1);
+ assert_eq!(*weak.upgrade().unwrap(), 10);
+
+ // Update the weak to point to rc2
+ weak.update(Rc::downgrade(&rc2));
+ assert_eq!(*weak.upgrade().unwrap(), 20);
+}
+
+#[test]
+fn test_arc_weak_update() {
+ let arc1 = Arc::new(10i32);
+ let arc2 = Arc::new(20i32);
+
+ let weak = ArcWeak::from(&arc1);
+ assert_eq!(*weak.upgrade().unwrap(), 10);
+
+ // Update the weak to point to arc2
+ weak.update(Arc::downgrade(&arc2));
+ assert_eq!(*weak.upgrade().unwrap(), 20);
+}
diff --git a/rust/tests/tests/test_refcell.rs b/rust/tests/tests/test_refcell.rs
new file mode 100644
index 000000000..61a94d45e
--- /dev/null
+++ b/rust/tests/tests/test_refcell.rs
@@ -0,0 +1,60 @@
+// 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.
+
+use fory_core::fory::Fory;
+use fory_derive::ForyObject;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[derive(ForyObject, Debug)]
+struct Simple {
+ value: i32,
+}
+
+#[test]
+fn test_rc_refcell_simple() {
+ let mut fury = Fory::default();
+ fury.register::<Simple>(3000);
+
+ let node = Rc::new(RefCell::new(Simple { value: 42 }));
+ let serialized = fury.serialize(&node);
+ let deserialized: Rc<RefCell<Simple>> =
fury.deserialize(&serialized).unwrap();
+ assert_eq!(deserialized.borrow().value, 42);
+}
+
+#[derive(ForyObject, Debug)]
+struct Parent {
+ value: i32,
+ child: Option<Rc<RefCell<Simple>>>,
+}
+
+#[test]
+fn test_rc_refcell_in_struct() {
+ let mut fury = Fory::default();
+ fury.register::<Simple>(3001);
+ fury.register::<Parent>(3002);
+
+ let child = Rc::new(RefCell::new(Simple { value: 99 }));
+ let parent = Parent {
+ value: 1,
+ child: Some(child),
+ };
+ let serialized = fury.serialize(&parent);
+ let deserialized: Parent = fury.deserialize(&serialized).unwrap();
+ assert_eq!(deserialized.value, 1);
+ assert_eq!(deserialized.child.unwrap().borrow().value, 99);
+}
diff --git a/rust/tests/tests/test_weak.rs b/rust/tests/tests/test_weak.rs
new file mode 100644
index 000000000..f1b52f7b9
--- /dev/null
+++ b/rust/tests/tests/test_weak.rs
@@ -0,0 +1,253 @@
+// 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.
+
+use fory_core::fory::Fory;
+use fory_core::serializer::weak::{ArcWeak, RcWeak};
+use fory_derive::ForyObject;
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+#[test]
+fn test_rc_weak_null_serialization() {
+ let fury = Fory::default();
+
+ let weak: RcWeak<i32> = RcWeak::new();
+
+ let serialized = fury.serialize(&weak);
+ let deserialized: RcWeak<i32> = fury.deserialize(&serialized).unwrap();
+
+ assert!(deserialized.upgrade().is_none());
+}
+
+#[test]
+fn test_arc_weak_null_serialization() {
+ let fury = Fory::default();
+
+ let weak: ArcWeak<i32> = ArcWeak::new();
+
+ let serialized = fury.serialize(&weak);
+ let deserialized: ArcWeak<i32> = fury.deserialize(&serialized).unwrap();
+
+ assert!(deserialized.upgrade().is_none());
+}
+
+#[test]
+fn test_rc_weak_dead_pointer_serializes_as_null() {
+ let fury = Fory::default();
+
+ let weak = {
+ let rc = Rc::new(42i32);
+ RcWeak::from(&rc)
+ // rc is dropped here
+ };
+
+ // Weak is now dead
+ assert!(weak.upgrade().is_none());
+
+ // Should serialize as Null
+ let serialized = fury.serialize(&weak);
+ let deserialized: RcWeak<i32> = fury.deserialize(&serialized).unwrap();
+
+ assert!(deserialized.upgrade().is_none());
+}
+
+#[test]
+fn test_arc_weak_dead_pointer_serializes_as_null() {
+ let fury = Fory::default();
+
+ let weak = {
+ let arc = Arc::new(String::from("test"));
+ ArcWeak::from(&arc)
+ // arc is dropped here
+ };
+
+ // Weak is now dead
+ assert!(weak.upgrade().is_none());
+
+ // Should serialize as Null
+ let serialized = fury.serialize(&weak);
+ let deserialized: ArcWeak<String> = fury.deserialize(&serialized).unwrap();
+
+ assert!(deserialized.upgrade().is_none());
+}
+
+#[test]
+fn test_rc_weak_in_vec_circular_reference() {
+ let fury = Fory::default();
+
+ let data1 = Rc::new(42i32);
+ let data2 = Rc::new(100i32);
+
+ let weak1 = RcWeak::from(&data1);
+ let weak2 = RcWeak::from(&data2);
+ let weak3 = weak1.clone();
+
+ let weaks = vec![weak1, weak2, weak3];
+ let serialized = fury.serialize(&weaks);
+ let deserialized: Vec<RcWeak<i32>> =
fury.deserialize(&serialized).unwrap();
+
+ assert_eq!(deserialized.len(), 3);
+}
+
+#[test]
+fn test_arc_weak_in_vec_circular_reference() {
+ let fury = Fory::default();
+
+ let data1 = Arc::new(String::from("hello"));
+ let data2 = Arc::new(String::from("world"));
+
+ let weak1 = ArcWeak::from(&data1);
+ let weak2 = ArcWeak::from(&data2);
+ let weak3 = weak1.clone();
+
+ let weaks = vec![weak1, weak2, weak3];
+ let serialized = fury.serialize(&weaks);
+ let deserialized: Vec<ArcWeak<String>> =
fury.deserialize(&serialized).unwrap();
+
+ assert_eq!(deserialized.len(), 3);
+}
+
+#[test]
+fn test_rc_weak_field_in_struct() {
+ use fory_derive::ForyObject;
+
+ #[derive(ForyObject, Debug)]
+ struct SimpleNode {
+ value: i32,
+ weak_ref: RcWeak<i32>,
+ }
+
+ let mut fury = Fory::default();
+ fury.register::<SimpleNode>(1000);
+
+ let data = Rc::new(42i32);
+ let node = SimpleNode {
+ value: 1,
+ weak_ref: RcWeak::from(&data),
+ };
+
+ let serialized = fury.serialize(&node);
+ let deserialized: SimpleNode = fury.deserialize(&serialized).unwrap();
+
+ assert_eq!(deserialized.value, 1);
+}
+
+#[derive(ForyObject, Debug)]
+struct Node {
+ value: i32,
+ // Weak ref to parent Rc<RefCell<Node>>
+ parent: RcWeak<RefCell<Node>>,
+ // Strong refs to children Rc<RefCell<Node>>
+ children: Vec<Rc<RefCell<Node>>>,
+}
+
+#[test]
+fn test_node_circular_reference_with_parent_children() {
+ // Register the Node type with Fory
+ let mut fury = Fory::default();
+ fury.register::<Node>(2000);
+
+ // Create parent
+ let parent = Rc::new(RefCell::new(Node {
+ value: 1,
+ parent: RcWeak::new(),
+ children: vec![],
+ }));
+
+ // Create children pointing back to parent via weak ref
+ let child1 = Rc::new(RefCell::new(Node {
+ value: 2,
+ parent: RcWeak::new(),
+ children: vec![],
+ }));
+
+ let child2 = Rc::new(RefCell::new(Node {
+ value: 3,
+ parent: RcWeak::new(),
+ children: vec![],
+ }));
+
+ // Add children to parent's children list
+ parent.borrow_mut().children.push(child1.clone());
+ parent.borrow_mut().children.push(child2.clone());
+
+ // Set children's parent weak refs to point back to parent (creating
circular reference)
+ child1.borrow_mut().parent = RcWeak::from(&parent);
+ child2.borrow_mut().parent = RcWeak::from(&parent);
+
+ // --- Serialize the parent node (will include children recursively) ---
+ let serialized = fury.serialize(&parent);
+
+ // --- Deserialize ---
+ let deserialized: Rc<RefCell<Node>> =
fury.deserialize(&serialized).unwrap();
+
+ // --- Verify ---
+ let des_parent = deserialized.borrow();
+ assert_eq!(des_parent.value, 1);
+ assert_eq!(des_parent.children.len(), 2);
+
+ for child in &des_parent.children {
+ let upgraded_parent = child.borrow().parent.upgrade();
+ assert!(upgraded_parent.is_some());
+ assert!(Rc::ptr_eq(&deserialized, &upgraded_parent.unwrap()));
+ }
+}
+
+#[test]
+fn test_arc_mutex_circular_reference() {
+ #[derive(ForyObject)]
+ struct Node {
+ val: i32,
+ parent: ArcWeak<Mutex<Node>>,
+ children: Vec<Arc<Mutex<Node>>>,
+ }
+
+ let mut fury = Fory::default();
+ fury.register::<Node>(6000);
+
+ let parent = Arc::new(Mutex::new(Node {
+ val: 10,
+ parent: ArcWeak::new(),
+ children: vec![],
+ }));
+
+ let child1 = Arc::new(Mutex::new(Node {
+ val: 20,
+ parent: ArcWeak::from(&parent),
+ children: vec![],
+ }));
+
+ let child2 = Arc::new(Mutex::new(Node {
+ val: 30,
+ parent: ArcWeak::from(&parent),
+ children: vec![],
+ }));
+
+ parent.lock().unwrap().children.push(child1.clone());
+ parent.lock().unwrap().children.push(child2.clone());
+
+ let serialized = fury.serialize(&parent);
+ let deserialized: Arc<Mutex<Node>> =
fury.deserialize(&serialized).unwrap();
+
+ assert_eq!(deserialized.lock().unwrap().children.len(), 2);
+ for child in &deserialized.lock().unwrap().children {
+ let upgraded_parent = child.lock().unwrap().parent.upgrade().unwrap();
+ assert!(Arc::ptr_eq(&deserialized, &upgraded_parent));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]