# graph-clonable-ref
[](https://crates.io/crates/graph-clonable-ref)
[](https://docs.rs/graph-clonable-ref)
[](https://opensource.org/licenses/MIT)
[](https://github.com/prizzledev/graph-clonable-ref/actions)
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 `RefGraph` caches 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 `Weak` refs 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 with `Arc`, actix-web, tokio, rayon, etc.
- **Panic-safe** — `DeepCloneGuard` with depth tracking resets state correctly on unwind
- **Fast** — generation-based O(1) cache hits, ~7ns per reference
- **Nestable** — `deep_clone` inside `deep_clone` works correctly
- **Zero boilerplate** — works with `#[derive(Clone)]`, no special traits needed
- **No `'static` bound** — works with any lifetime
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
graph-clonable-ref = "0.1"
```
## Usage
### Basic Example
```rust
use graph_clonable_ref::{RefGraph, GraphRef, deep_clone};
// Create a graph (container for related references)
let graph = RefGraph::new();
// Create references within the graph
let a = graph.create(42);
let b = a.clone(); // b points to same data as a
// Verify they share data
a.set(100);
assert_eq!(b.get(), 100);
// Deep clone preserves structure
let (a2, b2) = deep_clone(&(a.clone(), b.clone()));
// a2 and b2 point to the SAME new data
a2.set(999);
assert_eq!(b2.get(), 999);
// Original is unaffected
assert_eq!(a.get(), 100);
```
### In Structs
```rust
use graph_clonable_ref::{RefGraph, GraphRef, deep_clone};
#[derive(Clone)]
struct NeuralNetwork {
weights: Vec<GraphRef<f64>>,
biases: Vec<GraphRef<f64>>,
tied_weights: Vec<GraphRef<f64>>, // shares data with weights
}
impl NeuralNetwork {
fn new() -> Self {
let graph = RefGraph::new();
let weights: Vec<_> = (0..100).map(|_| graph.create(0.0)).collect();
let biases: Vec<_> = (0..10).map(|_| graph.create(0.0)).collect();
let tied_weights = weights[0..10].to_vec();
NeuralNetwork { weights, biases, tied_weights }
}
}
let net = NeuralNetwork::new();
let net2 = deep_clone(&net);
// 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)
```rust
use actix_web::{web, App, HttpServer, HttpResponse};
use graph_clonable_ref::{RefGraph, GraphRef, deep_clone};
#[derive(Clone)]
struct AppState {
weights: Vec<GraphRef<f64>>,
tied_weights: Vec<GraphRef<f64>>,
}
async fn train_handler(data: web::Data<AppState>) -> HttpResponse {
// Each request gets its own independent copy
let mut local = deep_clone(data.get_ref());
local.weights[0].set(1.0);
assert_eq!(local.tied_weights[0].get(), 1.0); // still tied
HttpResponse::Ok().finish()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let graph = RefGraph::new();
let weights: Vec<_> = (0..100).map(|i| graph.create(i as f64)).collect();
let tied_weights = weights[..10].to_vec();
let state = web::Data::new(AppState { weights, tied_weights });
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.route("/train", web::post().to(train_handler))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
```
### Multiple Graphs
References from different graphs remain independent:
```rust
use graph_clonable_ref::{RefGraph, GraphRef, deep_clone};
let graph1 = RefGraph::new();
let graph2 = RefGraph::new();
let a = graph1.create(1);
let b = graph1.create(2);
let c = graph2.create(3);
let (a2, b2, c2) = deep_clone(&(a.clone(), b.clone(), c.clone()));
// a2 and b2 share the same cloned graph
assert!(a2.same_graph(&b2));
// c2 is in a different cloned graph
assert!(!a2.same_graph(&c2));
```
## API Reference
### `RefGraph<T: Send + Sync>`
Container for a group of related references.
```rust
let graph = RefGraph::new(); // -> Arc<RefGraph<T>>
let r: GraphRef<i32> = graph.create(42); // 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.
```rust
let value = r.get(); // get value (requires T: Clone)
r.set(100); // set value
Benchmarks on a network with 5 layers x 500 references (3750 total with sharing):
| **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:
```bash
cargo bench
```
## How It Works
1. **Normal clone (`a.clone()`)**: Returns a new `GraphRef` pointing to the same data (shallow, like `Arc::clone`)
2. **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 `Weak` ref, return new ref
- `DeepCloneGuard` resets state on drop (even on panic)
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