SIRHAMY/light-clone

Compile-time enforcement for O(1) clone operations in Rust.

Rust

70

73 commits

updated Feb 1, 2026

See the code

README

LightClone

Compile-time enforcement for O(1) clone operations in Rust.

Overview

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:

  • Atomic refcount increments (Arc)
  • Non-atomic refcount increments (Rc)
  • Bitwise copies (Copy types)
  • Persistent data structures (im, imbl, rpds with structural sharing)

Types like String, Vec, or HashMap that perform deep copies are rejected at compile time.

crates.io | docs.rs

Usage

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();

Compile-Time Safety

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.

Ergonomic Strings

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

Supported Types

Primitives

All primitive types: i8-i128, u8-u128, f32, f64, bool, char, ()

Smart Pointers

  • Arc<T> where T: ?Sized
  • Rc<T> where T: ?Sized
  • std::sync::Weak<T> where T: ?Sized
  • std::rc::Weak<T> where T: ?Sized

Containers

  • Option<T> where T: LightClone
  • Result<T, E> where T: LightClone, E: LightClone
  • PhantomData<T>
  • Tuples up to 12 elements
  • [T; N] arrays where T: LightClone + Copy

Wrapper Types

  • Pin<T> where T: LightClone
  • Bound<T> where T: LightClone
  • Poll<T> where T: LightClone
  • ManuallyDrop<T> where T: LightClone
  • Cell<T> where T: LightClone + Copy
  • NonNull<T> where T: ?Sized

Function Pointers

  • fn(...) -> R with up to 12 arguments

Enums

#[derive(Clone, LightClone)]
enum State {
    Idle,
    Loading { progress: u8 },
    Ready(Arc<Data>),
}

Implementing for Custom Types

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:

  • Third-party types that are O(1) to clone but don't have built-in LightClone support
  • Newtypes wrapping LightClone types
  • Types from external crates where you can't use the derive macro

Features

Enable integrations with popular crates via feature flags:

[dependencies]
light_clone = { version = "0.3", features = ["bytes", "smol_str"] }

Persistent Collections

FeatureCrateTypes
imimVector, HashMap, HashSet, OrdMap, OrdSet
imblimblVector, HashMap, HashSet, OrdMap, OrdSet
rpdsrpdsVector, List, Queue, Stack, HashTrieMap, HashTrieSet, RedBlackTreeMap, RedBlackTreeSet

Common Types

FeatureCrateTypesClone Mechanism
bytesbytesBytesArc-based ref counting
smol_strsmol_strSmolStrInline or Arc
uuiduuidUuidCopy (128-bit)
rust_decimalrust_decimalDecimalCopy (128-bit)
ordered-floatordered-floatOrderedFloat<T>, NotNan<T>Copy wrapper
chronochronoNaiveDate, NaiveTime, NaiveDateTime, DateTime<Tz>, Month, Weekday, TimeDelta, Utc, FixedOffsetCopy
timetimeDate, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset, Duration, Month, WeekdayCopy

Meta Features

FeatureDescription
fullEnable all optional integrations

When to Use Immutable Data Structures

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:

  • Clone-heavy workloads - Sharing state across threads, event sourcing, undo/redo systems, functional pipelines with pure transforms
  • Cloning large or nested data - A 10KB string clone copies 10KB; an Arc<str> clone increments a counter
  • Concurrent code - Clone and send freely without worrying about data races or locks
  • Structural sharing matters - Persistent collections share unchanged portions between versions

The trade-offs to consider:

  • Mutation is still faster than cloning - LightClone enforces cloning is cheap, not free. In-place mutation avoids refcount operations entirely, so prefer mutation for hot paths
  • Memory overhead - Arc/Rc add pointer indirection and allocation overhead. Persistent collections trade memory for structural sharing

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.

Performance

LightClone has zero runtime overhead—.light_clone() compiles to identical code as .clone().

The real performance benefit comes from using immutable data structures:

ScenarioImmutableStandardDifference
Clone 10KB string11 ns83 ns7x faster
Clone struct with 50 levels of nesting15 ns1.9 µs128x faster
Clone 10K element vector41 ns622 ns15x faster
Clone 10K element hashmap15 ns2.2 µs148x 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.

Minimum Supported Rust Version

Rust 1.70.0. The rpds feature requires Rust 1.85+ due to upstream dependencies.

License

Licensed under either of:

at your option.

Created By

Hamilton Greene

Contributors

SIRHAMY

73 commits

SIRHAMY/light-clone

Compile-time enforcement for O(1) clone operations in Rust.

Rust

70

73 commits

updated Feb 1, 2026

See the code

README

LightClone

Compile-time enforcement for O(1) clone operations in Rust.

Overview

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:

  • Atomic refcount increments (Arc)
  • Non-atomic refcount increments (Rc)
  • Bitwise copies (Copy types)
  • Persistent data structures (im, imbl, rpds with structural sharing)

Types like String, Vec, or HashMap that perform deep copies are rejected at compile time.

crates.io | docs.rs

Usage

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();

Compile-Time Safety

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.

Ergonomic Strings

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

Supported Types

Primitives

All primitive types: i8-i128, u8-u128, f32, f64, bool, char, ()

Smart Pointers

  • Arc<T> where T: ?Sized
  • Rc<T> where T: ?Sized
  • std::sync::Weak<T> where T: ?Sized
  • std::rc::Weak<T> where T: ?Sized

Containers

  • Option<T> where T: LightClone
  • Result<T, E> where T: LightClone, E: LightClone
  • PhantomData<T>
  • Tuples up to 12 elements
  • [T; N] arrays where T: LightClone + Copy

Wrapper Types

  • Pin<T> where T: LightClone
  • Bound<T> where T: LightClone
  • Poll<T> where T: LightClone
  • ManuallyDrop<T> where T: LightClone
  • Cell<T> where T: LightClone + Copy
  • NonNull<T> where T: ?Sized

Function Pointers

  • fn(...) -> R with up to 12 arguments

Enums

#[derive(Clone, LightClone)]
enum State {
    Idle,
    Loading { progress: u8 },
    Ready(Arc<Data>),
}

Implementing for Custom Types

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:

  • Third-party types that are O(1) to clone but don't have built-in LightClone support
  • Newtypes wrapping LightClone types
  • Types from external crates where you can't use the derive macro

Features

Enable integrations with popular crates via feature flags:

[dependencies]
light_clone = { version = "0.3", features = ["bytes", "smol_str"] }

Persistent Collections

FeatureCrateTypes
imimVector, HashMap, HashSet, OrdMap, OrdSet
imblimblVector, HashMap, HashSet, OrdMap, OrdSet
rpdsrpdsVector, List, Queue, Stack, HashTrieMap, HashTrieSet, RedBlackTreeMap, RedBlackTreeSet

Common Types

FeatureCrateTypesClone Mechanism
bytesbytesBytesArc-based ref counting
smol_strsmol_strSmolStrInline or Arc
uuiduuidUuidCopy (128-bit)
rust_decimalrust_decimalDecimalCopy (128-bit)
ordered-floatordered-floatOrderedFloat<T>, NotNan<T>Copy wrapper
chronochronoNaiveDate, NaiveTime, NaiveDateTime, DateTime<Tz>, Month, Weekday, TimeDelta, Utc, FixedOffsetCopy
timetimeDate, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset, Duration, Month, WeekdayCopy

Meta Features

FeatureDescription
fullEnable all optional integrations

When to Use Immutable Data Structures

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:

  • Clone-heavy workloads - Sharing state across threads, event sourcing, undo/redo systems, functional pipelines with pure transforms
  • Cloning large or nested data - A 10KB string clone copies 10KB; an Arc<str> clone increments a counter
  • Concurrent code - Clone and send freely without worrying about data races or locks
  • Structural sharing matters - Persistent collections share unchanged portions between versions

The trade-offs to consider:

  • Mutation is still faster than cloning - LightClone enforces cloning is cheap, not free. In-place mutation avoids refcount operations entirely, so prefer mutation for hot paths
  • Memory overhead - Arc/Rc add pointer indirection and allocation overhead. Persistent collections trade memory for structural sharing

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.

Performance

LightClone has zero runtime overhead—.light_clone() compiles to identical code as .clone().

The real performance benefit comes from using immutable data structures:

ScenarioImmutableStandardDifference
Clone 10KB string11 ns83 ns7x faster
Clone struct with 50 levels of nesting15 ns1.9 µs128x faster
Clone 10K element vector41 ns622 ns15x faster
Clone 10K element hashmap15 ns2.2 µs148x 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.

Minimum Supported Rust Version

Rust 1.70.0. The rpds feature requires Rust 1.85+ due to upstream dependencies.

License

Licensed under either of:

at your option.

Created By

Hamilton Greene

Contributors

SIRHAMY

73 commits

Languages

Rust

91.5%

Shell

4.0%

Just

2.3%

Dockerfile

2.2%