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 ;
use DeepClone;
let shared = new;
let original = Diamond ;
let copy = original.deep_clone;
// One new object, reachable from both fields of the copy.
assert!;
assert!;
// Writing through the copy leaves the original alone.
*copy.left.borrow_mut = 2;
assert_eq!;
assert_eq!;
Box<dyn Trait> works too, 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 sameDeepClone::deep_clone, but itsRcimpl isRc::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-clonealso spells itDeepClone::deep_clone, but solves lifetime erasure: an associatedtype DeepCloned: 'staticturns aCow<'a, T>into aCow<'static, T>.dyn-clonemakesClonedyn-compatible without changing its semantics. This crate borrows its*mut ()technique.ImplicitCloneanddupeare about cloning cheaply, not deeply.forytracksRc/Arcidentity and cycles across a serialize and deserialize round-trip, and handlesRc<dyn Trait>without an impl of your own. Worth considering if you already haveSerialize/Deserializebounds.
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.