graph-clonable-ref
A fast, thread-safe cloneable reference type for Rust that preserves reference structure during deep cloning.
The Problem
When you have shared references (like Arc<RwLock<T>>) in a struct and clone it, each reference is cloned independently. References that pointed to the same data before cloning now point to different copies — structure is lost.
Traditional solutions use a HashMap<*const T, Arc<T>> during cloning to track which references have been cloned. This works but is slow (~17ns per reference lookup).
The Solution
This crate uses generation-based tracking instead of HashMap lookups:
- Each
RefGraphcaches its clone with a globally unique generation number - During
deep_clone(), a unique generation is assigned via atomic counter - References check if their graph's cached clone matches the current generation
- First reference triggers the clone, subsequent references get O(1) cache hits
- Cache entries use
Weakrefs and auto-expire when no longer referenced
Result: ~7 nanoseconds per reference — thread-safe, panic-safe, and 2.5x faster than HashMap.
Features
- Thread-safe —
Send + Sync, works withArc, actix-web, tokio, rayon, etc. - Panic-safe —
DeepCloneGuardwith depth tracking resets state correctly on unwind - Fast — generation-based O(1) cache hits, ~7ns per reference
- Nestable —
deep_cloneinsidedeep_cloneworks correctly - Zero boilerplate — works with
#[derive(Clone)], no special traits needed - No
'staticbound — works with any lifetime
Installation
Add this to your Cargo.toml:
[]
= "0.1"
Usage
Basic Example
use ;
// Create a graph (container for related references)
let graph = new;
// Create references within the graph
let a = graph.create;
let b = a.clone; // b points to same data as a
// Verify they share data
a.set;
assert_eq!;
// Deep clone preserves structure
let = deep_clone;
// a2 and b2 point to the SAME new data
a2.set;
assert_eq!;
// Original is unaffected
assert_eq!;
In Structs
use ;
let net = new;
let net2 = deep_clone;
// net2's tied_weights still reference the same data as net2's weights
// but are independent from net's weights
Thread-Safe Usage (actix-web)
use ;
use ;
async
async
Multiple Graphs
References from different graphs remain independent:
use ;
let graph1 = new;
let graph2 = new;
let a = graph1.create;
let b = graph1.create;
let c = graph2.create;
let = deep_clone;
// a2 and b2 share the same cloned graph
assert!;
// c2 is in a different cloned graph
assert!;
API Reference
RefGraph<T: Send + Sync>
Container for a group of related references.
let graph = new; // -> Arc<RefGraph<T>>
let r: = graph.create; // create a reference
let count = graph.len; // number of values
graph.clear_cache; // free clone cache memory
GraphRef<T: Send + Sync>
A cloneable reference that preserves structure during deep cloning.
let value = r.get; // get value (requires T: Clone)
r.set; // set value
r.update; // update with function
a.ptr_eq; // same graph and index?
a.same_graph; // same graph?
r.index; // index within graph
deep_clone<T: Clone>(value: &T) -> T
Performs a structure-preserving deep clone. Thread-safe and panic-safe.
begin_deep_clone() -> DeepCloneGuard
For manual control over the cloning scope (advanced). Supports nesting.
let _guard = begin_deep_clone;
let a2 = a.clone; // deep clone
let b2 = b.clone; // deep clone, same generation
// guard dropped -> back to shallow clone behavior
Performance
Benchmarks on a network with 5 layers x 500 references (3750 total with sharing):
| Approach | Time | Per Reference |
|---|---|---|
| Generation-based (this crate) | ~26 µs | ~7 ns |
| HashMap-based | ~64 µs | ~17 ns |
| Two-phase index (clone only) | ~105 µs | ~28 ns |
| Two-phase index (with prepare) | ~191 µs | ~51 ns |
Run benchmarks yourself:
How It Works
-
Normal clone (
a.clone()): Returns a newGraphRefpointing to the same data (shallow, likeArc::clone) -
Deep clone (
deep_clone(&x)):- Assigns a globally unique generation via
AtomicU64 - Calls
x.clone()which triggers special behavior - Each
GraphRef::clone()checks: does my graph have a cached clone for this generation?- Yes (read lock): Return a ref to the cached clone — O(1)
- No (write lock): Clone the graph, cache with
Weakref, return new ref
DeepCloneGuardresets state on drop (even on panic)
- Assigns a globally unique generation via
Cache entries use Weak<RefGraph<T>> so they auto-expire when the cloned graph is dropped. Dead entries are cleaned up lazily on cache-miss writes.
License
MIT