Expand description
Deep clone that copies shared data once.
Cloning a value that holds an Rc gives you two behaviours, neither of which is an
independent copy: #[derive(Clone)] bumps the reference count, so the copy shares
mutable state with the original, and a hand-written deep clone duplicates the pointee at
every reference, so two fields that pointed at one object now point at two.
This crate is the third behaviour: each object is copied once, and every reference to it in
the copy points at that one new object. It is copy.deepcopy from Python, and
algorithmically a copying garbage collector’s forwarding table. Serde documents the same
gap, warning that its rc feature “does not preserve identity and may result in multiple
copies of the same data”.
§The footgun
#[derive(Clone)] // Looks harmless. It is not.
struct Solver {
shared: Rc<RefCell<u32>>,
}
let original = Solver {
shared: Rc::new(RefCell::new(0)),
};
let copy = original.clone();
*copy.shared.borrow_mut() = 42;
// The "independent" copy wrote through to the original.
assert_eq!(*original.shared.borrow(), 42);Rc<T> implements Clone, so that compiles with no warning. Deriving DeepClone
instead routes every Rc through the cloner:
use deepclone::DeepClone;
#[derive(DeepClone)]
struct Solver {
shared: Rc<RefCell<u32>>,
}
let original = Solver {
shared: Rc::new(RefCell::new(0)),
};
let copy = original.deep_clone();
*copy.shared.borrow_mut() = 42;
assert_eq!(*original.shared.borrow(), 0);§Sharing is preserved
A field-by-field deep clone gets this part wrong. Two fields on one Rc must still be on
one Rc after the copy — a new one:
use deepclone::DeepClone;
#[derive(DeepClone)]
struct Diamond {
left: Rc<RefCell<u32>>,
right: Rc<RefCell<u32>>,
}
let shared = Rc::new(RefCell::new(1));
let original = Diamond {
left: Rc::clone(&shared),
right: Rc::clone(&shared),
};
let copy = original.deep_clone();
// One new object, reachable from both fields of the copy.
assert!(Rc::ptr_eq(©.left, ©.right));
assert!(!Rc::ptr_eq(©.left, &original.left));
*copy.left.borrow_mut() = 2;
assert_eq!(*copy.right.borrow(), 2);
assert_eq!(*original.right.borrow(), 1);§There is deliberately no blanket impl
impl<T: Clone> DeepClone for T would reintroduce the footgun, since Rc<T>: Clone and
without specialization such an impl could not be overridden for Rc. So a field whose type
has no DeepClone impl is a compile error.
An Rc only needs copying when something reachable through it can be mutated. When nothing
can, sharing the original allocation is unobservable and cheaper, and #[deepclone(clone)]
says so at the field, or on the type when that holds of all of it. No bound can express
this: Freeze is the nearest marker and it sees only interior mutability that is not behind
a pointer.
§Trait objects
DeepClone::deep_clone_in returns Self, so it is not dyn-compatible. Naming
DynDeepClone as a supertrait is all a trait needs for Box<dyn Trait> to deep clone,
auto-trait variants included:
use deepclone::{DeepClone, DynDeepClone};
trait Propagator: DynDeepClone {
fn state(&self) -> Rc<RefCell<u32>>;
}
#[derive(DeepClone)]
struct Counter(Rc<RefCell<u32>>);
impl Propagator for Counter {
fn state(&self) -> Rc<RefCell<u32>> {
Rc::clone(&self.0)
}
}
let shared = Rc::new(RefCell::new(0));
let original: Vec<Box<dyn Propagator>> = vec![
Box::new(Counter(Rc::clone(&shared))),
Box::new(Counter(Rc::clone(&shared))),
];
let copy = original.deep_clone();
// Two new propagators, sharing one new state object.
assert!(Rc::ptr_eq(©[0].state(), ©[1].state()));
assert!(!Rc::ptr_eq(©[0].state(), &shared));§Cycles
Strong Rc edges downwards with Weak back-edges upwards clones
correctly, cycles included: a Weak needs only its target’s identity, and
Rc::new_cyclic reserves the copy’s allocation before the pointee is built. A cycle of
strong edges panics instead of overflowing the stack; it leaks in the original too, so it
is a bug in the source.
§Scope, and what is not supported
One Cloner is one clone operation, and DeepClone::deep_clone makes a fresh one per
call. Reusing one for an unrelated clone is the one way to make two copies share again.
- Unsized pointees vary.
Rc<str>and itsCStr,OsStr, andPathsiblings share the source’s allocation, which nothing can observe.Rc<[T]>is copied but cannot join a cycle.Rc<dyn Trait>can have no impl at all, so namedeep_clone_unsized_rcat the field with#[deepclone(with = ..)]. - The
'staticfromTypeIdpropagates: a generic type with anRc<..T..>field needsT: 'staticon its own declaration, which the derive does not add for you. Cloneris neitherSendnorSync, soCloner::arcis single-threaded.- Peak memory holds both copies, since the
Clonerkeeps every copy alive. - A
RefCellalready mutably borrowed panics onborrow(), as does a poisonedMutexorRwLock. - Do not mutate the source from inside a
DeepCloneimpl. Objects are keyed by address, which is unambiguous only because every source stays alive throughout.
The derive is behind the default derive feature; the library builds without it.
§Prior art
The *mut () round-trip behind deep_clone_unsized_rc is David Tolnay’s, from
dyn-clone, and
oxc_allocator::CloneIn is the precedent for a context-carrying clone trait. The README
covers how this differs from the similarly named crates.
Structs§
- Cloner
- The forwarding table for one deep clone operation, mapping each source object’s identity to its copy.
Traits§
- Deep
Clone - A clone that copies everything reachable, preserving the sharing among the copies: two references to one object become two references to one new object, and the copy shares nothing with the original.
- DynDeep
Clone - Supertrait that lets a trait object be deep cloned.
Functions§
- deep_
clone_ unsized_ arc - Deep clone an
Arc<dyn YourTrait>, asdeep_clone_unsized_rcdoes forRc. - deep_
clone_ unsized_ rc - Deep clone an
Rc<dyn YourTrait>.
Derive Macros§
- Deep
Clone - Derive
DeepClone, cloning every field through the sameCloner.