Skip to main content

Crate deepclone

Crate deepclone 

Source
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(&copy.left, &copy.right));
assert!(!Rc::ptr_eq(&copy.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(&copy[0].state(), &copy[1].state()));
assert!(!Rc::ptr_eq(&copy[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 its CStr, OsStr, and Path siblings 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 name deep_clone_unsized_rc at the field with #[deepclone(with = ..)].
  • The 'static from TypeId propagates: a generic type with an Rc<..T..> field needs T: 'static on its own declaration, which the derive does not add for you.
  • Cloner is neither Send nor Sync, so Cloner::arc is single-threaded.
  • Peak memory holds both copies, since the Cloner keeps every copy alive.
  • A RefCell already mutably borrowed panics on borrow(), as does a poisoned Mutex or RwLock.
  • Do not mutate the source from inside a DeepClone impl. 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§

DeepClone
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.
DynDeepClone
Supertrait that lets a trait object be deep cloned.

Functions§

deep_clone_unsized_arc
Deep clone an Arc<dyn YourTrait>, as deep_clone_unsized_rc does for Rc.
deep_clone_unsized_rc
Deep clone an Rc<dyn YourTrait>.

Derive Macros§

DeepClone
Derive DeepClone, cloning every field through the same Cloner.