Compile-time enforcement for O(1) clone operations in Rust.
Rust
70
73 commits
updated Feb 1, 2026
Compile-time enforcement for O(1) clone operations in Rust.
LightClone is for codebases that embrace immutable data structures. It provides a marker trait and derive macro that guarantees cloning is cheap by only allowing types where cloning involves:
Arc)Rc)Copy types)Types like String, Vec, or HashMap that perform deep copies are rejected at compile time.
use light_clone::LightClone;
use std::sync::Arc;
#[derive(Clone, LightClone)]
struct Config {
name: Arc<str>,
max_connections: u32,
timeout_ms: u64,
}
let config = Config {
name: "production".into(),
max_connections: 100,
timeout_ms: 5000,
};
// .light_clone() or .lc() for short
let clone = config.lc();
Add #[derive(Clone, LightClone)] to your structs and enums. All fields must implement LightClone:
use light_clone::LightClone;
use std::sync::Arc;
#[derive(Clone, LightClone)]
struct MyType {
id: u64, // Copy types work
name: Arc<str>, // Arc works
metadata: Option<Arc<str>>, // Containers of LightClone work
}
If any field doesn't implement LightClone, you get a compile error:
#[derive(Clone, LightClone)]
struct Invalid {
data: String, // Error: String does not implement LightClone
}
This ensures your types remain O(1) to clone as they evolve.
Use LightStr as a cheap-to-clone string type:
use light_clone::{LightStr, IntoLightStr};
let s: LightStr = "hello".into_light_str();
let clone = s.lc(); // O(1) - just increments refcount
All primitive types: i8-i128, u8-u128, f32, f64, bool, char, ()
Arc<T> where T: ?SizedRc<T> where T: ?Sizedstd::sync::Weak<T> where T: ?Sizedstd::rc::Weak<T> where T: ?SizedOption<T> where T: LightCloneResult<T, E> where T: LightClone, E: LightClonePhantomData<T>[T; N] arrays where T: LightClone + CopyPin<T> where T: LightCloneBound<T> where T: LightClonePoll<T> where T: LightCloneManuallyDrop<T> where T: LightCloneCell<T> where T: LightClone + CopyNonNull<T> where T: ?Sizedfn(...) -> R with up to 12 arguments#[derive(Clone, LightClone)]
enum State {
Idle,
Loading { progress: u8 },
Ready(Arc<Data>),
}
Since LightClone is a marker trait, you can implement it for your own types or third-party types that are O(1) to clone:
use light_clone::LightClone;
use std::sync::Arc;
// For a type you know is O(1) to clone
struct MyArcWrapper(Arc<str>);
impl Clone for MyArcWrapper {
fn clone(&self) -> Self {
MyArcWrapper(self.0.clone())
}
}
impl LightClone for MyArcWrapper {}
The trait provides default implementations for light_clone() and lc() that delegate to clone(), so an empty impl is all you need.
This is useful for:
Enable integrations with popular crates via feature flags:
[dependencies]
light_clone = { version = "0.3", features = ["bytes", "smol_str"] }
| Feature | Crate | Types |
|---|---|---|
im | im | Vector, HashMap, HashSet, OrdMap, OrdSet |
imbl | imbl | Vector, HashMap, HashSet, OrdMap, OrdSet |
rpds | rpds | Vector, List, Queue, Stack, HashTrieMap, HashTrieSet, RedBlackTreeMap, RedBlackTreeSet |
| Feature | Crate | Types | Clone Mechanism |
|---|---|---|---|
bytes | bytes | Bytes | Arc-based ref counting |
smol_str | smol_str | SmolStr | Inline or Arc |
uuid | uuid | Uuid | Copy (128-bit) |
rust_decimal | rust_decimal | Decimal | Copy (128-bit) |
ordered-float | ordered-float | OrderedFloat<T>, NotNan<T> | Copy wrapper |
chrono | chrono | NaiveDate, NaiveTime, NaiveDateTime, DateTime<Tz>, Month, Weekday, TimeDelta, Utc, FixedOffset | Copy |
time | time | Date, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset, Duration, Month, Weekday | Copy |
| Feature | Description |
|---|---|
full | Enable all optional integrations |
LightClone enforces that your types use immutable data structures (Arc, Rc, persistent collections) which enable O(1) cloning through structural sharing. This approach shines when:
Arc<str> clone increments a counterThe trade-offs to consider:
Where LightClone fits: Once you've committed to immutable data structures, LightClone provides compile-time enforcement that cloning is cheap. It catches accidental String or Vec fields that would silently introduce expensive deep clones.
LightClone has zero runtime overhead—.light_clone() compiles to identical code as .clone().
The real performance benefit comes from using immutable data structures:
| Scenario | Immutable | Standard | Difference |
|---|---|---|---|
| Clone 10KB string | 11 ns | 83 ns | 7x faster |
| Clone struct with 50 levels of nesting | 15 ns | 1.9 µs | 128x faster |
| Clone 10K element vector | 41 ns | 622 ns | 15x faster |
| Clone 10K element hashmap | 15 ns | 2.2 µs | 148x faster |
Mutation has trade-offs—persistent collections are slower for small, mutation-heavy workloads but catch up as data grows. See BENCHMARKS.md for detailed comparisons.
Rust 1.70.0. The rpds feature requires Rust 1.85+ due to upstream dependencies.
Licensed under either of:
at your option.
73 commits
Rust
91.5%
Shell
4.0%
Just
2.3%
Dockerfile
2.2%
Compile-time enforcement for O(1) clone operations in Rust.
Rust
70
73 commits
updated Feb 1, 2026
Compile-time enforcement for O(1) clone operations in Rust.
LightClone is for codebases that embrace immutable data structures. It provides a marker trait and derive macro that guarantees cloning is cheap by only allowing types where cloning involves:
Arc)Rc)Copy types)Types like String, Vec, or HashMap that perform deep copies are rejected at compile time.
use light_clone::LightClone;
use std::sync::Arc;
#[derive(Clone, LightClone)]
struct Config {
name: Arc<str>,
max_connections: u32,
timeout_ms: u64,
}
let config = Config {
name: "production".into(),
max_connections: 100,
timeout_ms: 5000,
};
// .light_clone() or .lc() for short
let clone = config.lc();
Add #[derive(Clone, LightClone)] to your structs and enums. All fields must implement LightClone:
use light_clone::LightClone;
use std::sync::Arc;
#[derive(Clone, LightClone)]
struct MyType {
id: u64, // Copy types work
name: Arc<str>, // Arc works
metadata: Option<Arc<str>>, // Containers of LightClone work
}
If any field doesn't implement LightClone, you get a compile error:
#[derive(Clone, LightClone)]
struct Invalid {
data: String, // Error: String does not implement LightClone
}
This ensures your types remain O(1) to clone as they evolve.
Use LightStr as a cheap-to-clone string type:
use light_clone::{LightStr, IntoLightStr};
let s: LightStr = "hello".into_light_str();
let clone = s.lc(); // O(1) - just increments refcount
All primitive types: i8-i128, u8-u128, f32, f64, bool, char, ()
Arc<T> where T: ?SizedRc<T> where T: ?Sizedstd::sync::Weak<T> where T: ?Sizedstd::rc::Weak<T> where T: ?SizedOption<T> where T: LightCloneResult<T, E> where T: LightClone, E: LightClonePhantomData<T>[T; N] arrays where T: LightClone + CopyPin<T> where T: LightCloneBound<T> where T: LightClonePoll<T> where T: LightCloneManuallyDrop<T> where T: LightCloneCell<T> where T: LightClone + CopyNonNull<T> where T: ?Sizedfn(...) -> R with up to 12 arguments#[derive(Clone, LightClone)]
enum State {
Idle,
Loading { progress: u8 },
Ready(Arc<Data>),
}
Since LightClone is a marker trait, you can implement it for your own types or third-party types that are O(1) to clone:
use light_clone::LightClone;
use std::sync::Arc;
// For a type you know is O(1) to clone
struct MyArcWrapper(Arc<str>);
impl Clone for MyArcWrapper {
fn clone(&self) -> Self {
MyArcWrapper(self.0.clone())
}
}
impl LightClone for MyArcWrapper {}
The trait provides default implementations for light_clone() and lc() that delegate to clone(), so an empty impl is all you need.
This is useful for:
Enable integrations with popular crates via feature flags:
[dependencies]
light_clone = { version = "0.3", features = ["bytes", "smol_str"] }
| Feature | Crate | Types |
|---|---|---|
im | im | Vector, HashMap, HashSet, OrdMap, OrdSet |
imbl | imbl | Vector, HashMap, HashSet, OrdMap, OrdSet |
rpds | rpds | Vector, List, Queue, Stack, HashTrieMap, HashTrieSet, RedBlackTreeMap, RedBlackTreeSet |
| Feature | Crate | Types | Clone Mechanism |
|---|---|---|---|
bytes | bytes | Bytes | Arc-based ref counting |
smol_str | smol_str | SmolStr | Inline or Arc |
uuid | uuid | Uuid | Copy (128-bit) |
rust_decimal | rust_decimal | Decimal | Copy (128-bit) |
ordered-float | ordered-float | OrderedFloat<T>, NotNan<T> | Copy wrapper |
chrono | chrono | NaiveDate, NaiveTime, NaiveDateTime, DateTime<Tz>, Month, Weekday, TimeDelta, Utc, FixedOffset | Copy |
time | time | Date, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset, Duration, Month, Weekday | Copy |
| Feature | Description |
|---|---|
full | Enable all optional integrations |
LightClone enforces that your types use immutable data structures (Arc, Rc, persistent collections) which enable O(1) cloning through structural sharing. This approach shines when:
Arc<str> clone increments a counterThe trade-offs to consider:
Where LightClone fits: Once you've committed to immutable data structures, LightClone provides compile-time enforcement that cloning is cheap. It catches accidental String or Vec fields that would silently introduce expensive deep clones.
LightClone has zero runtime overhead—.light_clone() compiles to identical code as .clone().
The real performance benefit comes from using immutable data structures:
| Scenario | Immutable | Standard | Difference |
|---|---|---|---|
| Clone 10KB string | 11 ns | 83 ns | 7x faster |
| Clone struct with 50 levels of nesting | 15 ns | 1.9 µs | 128x faster |
| Clone 10K element vector | 41 ns | 622 ns | 15x faster |
| Clone 10K element hashmap | 15 ns | 2.2 µs | 148x faster |
Mutation has trade-offs—persistent collections are slower for small, mutation-heavy workloads but catch up as data grows. See BENCHMARKS.md for detailed comparisons.
Rust 1.70.0. The rpds feature requires Rust 1.85+ due to upstream dependencies.
Licensed under either of:
at your option.
73 commits
Rust
91.5%
Shell
4.0%
Just
2.3%
Dockerfile
2.2%