deepclone 0.3.1

Deep clone that copies shared data once: an `Rc`/`Arc` reached twice becomes one new `Rc`/`Arc`
Documentation

deepclone

Deep clone that copies shared data once.

#[derive(Clone)] on a type holding an Rc bumps the reference count, so the "copy" writes through to the original. A hand-written deep clone goes wrong the other way, duplicating the pointee at every reference, so what was shared once becomes two separate copies. This crate is the third behaviour: each object is copied once, every reference to it in the copy points at that one new object, and the copy shares nothing with the original. It is Python's copy.deepcopy.

use std::{cell::RefCell, rc::Rc};
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));

// Writing through the copy leaves the original alone.
*copy.left.borrow_mut() = 2;
assert_eq!(*copy.right.borrow(), 2);
assert_eq!(*original.right.borrow(), 1);

Box<dyn Trait> works too, needing nothing but a DynDeepClone supertrait bound, and so do Weak back-edges and the cycles they form. See the crate docs for those, for the limits, and for why there is deliberately no blanket impl<T: Clone> DeepClone for T.

Requires Rust 1.85. The derive feature is on by default; the library builds without it.

Not to be confused with

Two crates share this one's trait name, and one of them has the opposite semantics.

  • deep-clone (unmaintained since 2022) declares the same DeepClone::deep_clone, but its Rc impl is Rc::new(self.deref().deep_clone()). With nothing tracking what it has already copied, a diamond becomes two allocations. That is the failure this crate exists to avoid, shipped under this crate's trait name.
  • asajeffrey/deep-clone also spells it DeepClone::deep_clone, but solves lifetime erasure: an associated type DeepCloned: 'static turns a Cow<'a, T> into a Cow<'static, T>.
  • dyn-clone makes Clone dyn-compatible without changing its semantics. This crate borrows its *mut () technique.
  • ImplicitClone and dupe are about cloning cheaply, not deeply.
  • fory tracks Rc/Arc identity and cycles across a serialize and deserialize round-trip, and handles Rc<dyn Trait> without an impl of your own. Worth considering if you already have Serialize/Deserialize bounds.

What it costs

Medians from cargo bench -- --sample-count 1000 on an Apple M4.

Clone deep_clone naive deep clone
A struct with no shared pointers ~105 ns ~105 ns n/a
16 nodes sharing a 64-node subtree n/a 4.9 µs 22.7 µs
A chain of 256 unique nodes n/a 16.7 µs 5.7 µs

n/a marks what a column cannot do: Clone never makes an independent copy, and with no shared pointers a naive deep clone is this one.

The trade against a naive deep clone is one hash and one insert per Rc, about 45 ns, in exchange for skipping every repeated visit to a shared object. The more sharing, the further ahead this gets; with none, it is a straight loss.

The first row is a wash and allocation-dominated. Measure it on its own if you care, since neighbouring benchmark groups move it by more than the difference.

Licence

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 licence, shall be dual licensed as above, without any additional terms or conditions.