rust-key-paths 1.18.0

Keypaths for Rust: Static dispatch implementation (rust-keypaths) and legacy dynamic dispatch (key-paths-core). Type-safe, composable access to nested data structures.
Documentation

🔑 KeyPaths in Rust

Key paths provide a safe, composable way to access and modify nested data in Rust. Inspired by **Swift's KeyPath ** system, this feature rich crate lets you work with struct fields and enum variants as first-class values.


🚀 New: Static Dispatch Implementation

rust-keypaths + keypaths-proc (Recommended)

  • Static dispatch - Faster performance, better compiler optimizations
  • Write operations can be faster than manual unwrapping at deeper nesting levels
  • Zero runtime overhead - No dynamic dispatch costs
  • Better inlining - Compiler can optimize more aggressively
  • Functional chains for Arc<Mutex<T>>/Arc<RwLock<T>> - Compose keypaths through sync primitives
  • parking_lot support - Optional feature for faster locks
[dependencies]
rust-keypaths = "1.4.0"
keypaths-proc = "1.4.0"

✨ Features

  • Readable/Writable keypaths for struct fields
  • Failable keypaths for Option<T> chains (_fr/_fw)
  • Enum CasePaths (readable and writable prisms)
  • Composition across structs, options and enum cases
  • Iteration helpers over collections via keypaths
  • Proc-macros: #[derive(Keypaths)] for structs/tuple-structs and enums, #[derive(Casepaths)] for enums
  • Functional chains for Arc<Mutex<T>> and Arc<RwLock<T>> - Compose-first, apply-later pattern
  • parking_lot support - Feature-gated support for faster synchronization primitives

🚀 Examples

Deep Nested Composition with Box and Enums

This example demonstrates keypath composition through deeply nested structures with Box<T> and enum variants:

use keypaths_proc::{Casepaths, Keypaths};

#[derive(Debug, Keypaths)]
#[Writable]
struct SomeComplexStruct {
    scsf: Box<SomeOtherStruct>,
}

impl SomeComplexStruct {
    fn new() -> Self {
        Self {
            scsf: Box::new(SomeOtherStruct {
                sosf: OneMoreStruct {
                    omsf: String::from("no value for now"),
                    omse: SomeEnum::B(DarkStruct {
                        dsf: String::from("dark field"),
                    }),
                },
            }),
        }
    }
}

#[derive(Debug, Keypaths)]
#[Writable]
struct SomeOtherStruct {
    sosf: OneMoreStruct,
}

#[derive(Debug, Casepaths)]
#[Writable]
enum SomeEnum {
    A(String),
    B(DarkStruct),
}

#[derive(Debug, Keypaths)]
#[Writable]
struct OneMoreStruct {
    omsf: String,
    omse: SomeEnum,
}

#[derive(Debug, Keypaths)]
#[Writable]
struct DarkStruct {
    dsf: String,
}

fn main() {
    use rust_keypaths::WritableOptionalKeyPath;
    
    // Compose keypath through Box, nested structs, and enum variants
    // Using .then() method (works on stable Rust)
    let keypath = SomeComplexStruct::scsf_fw()
        .then(SomeOtherStruct::sosf_fw())
        .then(OneMoreStruct::omse_fw())
        .then(SomeEnum::b_case_fw())
        .then(DarkStruct::dsf_fw());
    
    // Alternatively, use the >> operator (requires nightly feature):
    // #![feature(impl_trait_in_assoc_type)]
    // let keypath = SomeComplexStruct::scsf_fw()
    //     >> SomeOtherStruct::sosf_fw()
    //     >> OneMoreStruct::omse_fw()
    //     >> SomeEnum::b_case_fw()
    //     >> DarkStruct::dsf_fw();
    
    let mut instance = SomeComplexStruct::new();
    
    // Mutate deeply nested field through composed keypath
    if let Some(dsf) = keypath.get_mut(&mut instance) {
        *dsf = String::from("we can update the field of struct with the other way unlocked by keypaths");
        println!("instance = {:?}", instance);
    }
}

parking_lot Support (Default for Mutex/RwLock)

⚠️ IMPORTANT: When using the derive macro, Mutex and RwLock default to parking_lot unless you explicitly use std::sync::Mutex or std::sync::RwLock.

[dependencies]
rust-keypaths = { version = "1.4.0", features = ["parking_lot"] }

Derive Macro Generated Methods for Locks

The derive macro generates helper methods for Arc<Mutex<T>> and Arc<RwLock<T>> fields:

Field Type Generated Methods Description
Arc<Mutex<T>> (parking_lot default) _r(), _w(), _fr_at(kp), _fw_at(kp) Chain through parking_lot::Mutex
Arc<RwLock<T>> (parking_lot default) _r(), _w(), _fr_at(kp), _fw_at(kp) Chain through parking_lot::RwLock
Arc<std::sync::Mutex<T>> _r(), _w(), _fr_at(kp), _fw_at(kp) Chain through std::sync::Mutex
Arc<std::sync::RwLock<T>> _r(), _w(), _fr_at(kp), _fw_at(kp) Chain through std::sync::RwLock
use std::sync::Arc;
use parking_lot::RwLock;
use keypaths_proc::Keypaths;

#[derive(Keypaths)]
#[Writable]
struct Container {
    // This uses parking_lot::RwLock (default)
    data: Arc<RwLock<DataStruct>>,
    
    // This uses std::sync::RwLock (explicit)
    std_data: Arc<std::sync::RwLock<DataStruct>>,
}

#[derive(Keypaths)]
#[Writable]
struct DataStruct {
    name: String,
}

fn main() {
    let container = Container { /* ... */ };
    
    // Using generated _fr_at() for parking_lot (default)
    Container::data_fr_at(DataStruct::name_r())
        .get(&container, |value| {
            println!("Name: {}", value);
        });
    
    // Using generated _fw_at() for parking_lot (default)
    Container::data_fw_at(DataStruct::name_w())
        .get_mut(&container, |value| {
            *value = "New name".to_string();
        });
    
    // Using generated _fr_at() for std::sync::RwLock (explicit)
    Container::std_data_fr_at(DataStruct::name_r())
        .get(&container, |value| {
            println!("Name: {}", value);
        });
}

Key advantage: parking_lot locks never fail (no poisoning), so chain methods don't return Option for the lock operation itself.

Running the example:

cargo run --example parking_lot_chains --features parking_lot
cargo run --example parking_lot_nested_chain --features parking_lot

🌟 Showcase - Crates Using rust-key-paths

The rust-key-paths library is being used by several exciting crates in the Rust ecosystem:


🔗 Helpful Links & Resources


💡 Why use KeyPaths?

  • Avoids repetitive match / . chains.
  • Encourages compositional design.
  • Plays well with DDD (Domain-Driven Design) and Actor-based systems.
  • Useful for reflection-like behaviors in Rust (without unsafe).
  • High performance: essentially zero overhead for deep nested writes (10 levels)!

⚡ Performance

KeyPaths are optimized for performance with minimal overhead. Below are benchmark results comparing direct unwrap vs keypaths for 10-level deep nested access:

Operation Direct Unwrap KeyPath Notes
Read (10 levels) 384.07 ps 848.27 ps ~464 ps absolute difference
Write (10 levels) 19.306 ns 19.338 ns Essentially identical!

See benches/BENCHMARK_SUMMARY.md for detailed performance analysis.


🔄 Comparison with Other Lens Libraries

Feature rust-keypaths keypath pl-lens lens-rs
Struct Field Access ✅ Readable/Writable ✅ Readable/Writable ✅ Readable/Writable ✅ Partial
Option Chains ✅ Built-in (_fr/_fw) ❌ Manual composition ❌ Manual composition ❌ Manual
Enum Case Paths ✅ Built-in (CasePaths) ❌ Not supported ❌ Not supported ❌ Limited
Tuple Structs ✅ Full support ⚠️ Unknown ❌ Not supported ❌ Not supported
Composition .then() chaining ⚠️ Less ergonomic ⚠️ Manual ⚠️ Complex
Result<T, E> ✅ Built-in support ❌ Not supported ❌ Not supported ❌ Not supported
Mutex/RwLock ✅ Built-in (with_mutex, etc.) ❌ Not supported ❌ Not supported ❌ Not supported
Arc/Box/Rc ✅ Built-in support ⚠️ Unknown ⚠️ Limited ⚠️ Limited
Collections ✅ Vec, HashMap, HashSet, etc. ❌ Not supported ❌ Not supported ❌ Not supported
Derive Macros #[derive(Keypaths)], #[derive(Casepaths)] #[derive(Keypath)] #[derive(Lenses)] ⚠️ Limited
Deep Nesting ✅ Works seamlessly ⚠️ May require workarounds ❌ Requires workarounds ❌ Complex
Type Safety ✅ Full compile-time checks ✅ Good ✅ Good ⚠️ Moderate
Performance ✅ Optimized (1.46x overhead reads, near-zero writes) ⚠️ Unknown ⚠️ Unknown ⚠️ Unknown
Readable Keypaths KeyPath ✅ Supported RefLens ⚠️ Partial
Writable Keypaths WritableKeyPath ✅ Supported Lens ⚠️ Partial
Failable Readable OptionalKeyPath ❌ Manual ❌ Manual ❌ Manual
Failable Writable WritableOptionalKeyPath ❌ Manual ❌ Manual ❌ Manual
Zero-cost Abstractions ✅ Static dispatch ⚠️ Unknown ⚠️ Depends ⚠️ Depends
Swift KeyPath-like API ✅ Inspired by Swift ⚠️ Partial ❌ No ❌ No
Container Methods with_mutex, with_rwlock, with_arc, etc. ❌ Not supported ❌ Not supported ❌ Not supported
Iteration Helpers iter(), iter_mut() ❌ Not supported ❌ Not supported ❌ Not supported
Derivable References ✅ Full support ✅ Full support ❌ Not supported ❌ Not supported
Active Maintenance ✅ Active ⚠️ Unknown ⚠️ Unknown ⚠️ Unknown

Key Advantages of rust-keypaths

  1. ✅ Native Option support: Built-in failable keypaths (_fr/_fw) that compose seamlessly through Option<T> chains (unlike keypath, pl-lens, and lens-rs which require manual composition)
  2. ✅ Enum CasePaths: First-class support for enum variant access (prisms) with #[derive(Casepaths)] (unique feature not found in keypath, pl-lens, or lens-rs)
  3. ✅ Container types: Built-in support for Result, Mutex, RwLock, Arc, Rc, Box, and all standard collections (comprehensive container support unmatched by alternatives)
  4. ✅ Functional chains for sync primitives: Compose keypaths through Arc<Mutex<T>> and Arc<RwLock<T>> with a clean, functional API
  5. ✅ parking_lot support: Feature-gated support for faster parking_lot::Mutex and parking_lot::RwLock
  6. ✅ Zero-cost abstractions: Static dispatch with minimal overhead (1.46x for reads, near-zero for writes) - benchmarked and optimized
  7. ✅ Comprehensive derive macros: Automatic generation for structs (named and tuple), enums, and all container types
  8. ✅ Swift-inspired API: Familiar API for developers coming from Swift's KeyPath system with .then() composition
  9. ✅ Deep composition: Works seamlessly with 10+ levels of nesting without workarounds (tested and verified)
  10. ✅ Type-safe composition: Full compile-time type checking with .then() method
  11. ✅ Active development: Regularly maintained with comprehensive feature set and documentation

🛠 Roadmap

  • Compose across structs, options and enum cases
  • Derive macros for automatic keypath generation (Keypaths, Keypaths, Casepaths)
  • Optional chaining with failable keypaths
  • Smart pointer adapters (.for_arc(), .for_box(), .for_rc())
  • Container support for Result, Mutex, RwLock, Weak, and collections
  • Helper derive macros (ReadableKeypaths, WritableKeypaths)
  • Functional chains for Arc<Mutex<T>> and Arc<RwLock<T>>
  • parking_lot support for faster synchronization primitives
  • Derive macros for complex multi-field enum variants

📜 License

  • Mozilla Public License 2.0