rust-key-paths 1.11.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 & CasePaths in Rust

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


🚀 New: Static Dispatch Implementation

We now provide two implementations:

Primary: 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
[dependencies]
rust-keypaths = "1.0.2"
keypaths-proc = "1.0.1"

Legacy: key-paths-core + key-paths-derive (v1.6.0)

  • ⚠️ Dynamic dispatch - Use only if you need:
    • Send + Sync bounds for multithreaded scenarios
    • Dynamic dispatch with trait objects
    • Compatibility with existing code using the enum-based API
[dependencies]
key-paths-core = "1.6.0"  # Use 1.6.0 for dynamic dispatch
key-paths-derive = "1.1.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

📦 Installation

Recommended: Static Dispatch (rust-keypaths)

[dependencies]
rust-keypaths = "1.0.0"
keypaths-proc = "1.0.0"

Legacy: Dynamic Dispatch (key-paths-core)

[dependencies]
key-paths-core = "1.6.0"  # Use 1.6.0 for dynamic dispatch
key-paths-derive = "1.1.0"

API Differences

rust-keypaths (Static Dispatch):

use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath};

let kp = KeyPath::new(|s: &Struct| &s.field);
let opt_kp = OptionalKeyPath::new(|s: &Struct| s.opt_field.as_ref());
let writable_kp = WritableKeyPath::new(|s: &mut Struct| &mut s.field);

key-paths-core (Dynamic Dispatch):

use key_paths_core::KeyPaths;

let kp = KeyPaths::readable(|s: &Struct| &s.field);
let opt_kp = KeyPaths::failable_readable(|s: &Struct| s.opt_field.as_ref());
let writable_kp = KeyPaths::writable(|s: &mut Struct| &mut s.field);

🚀 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
    let keypath = SomeComplexStruct::scsf_fw()
        .then(SomeOtherStruct::sosf_fw())
        .then(OneMoreStruct::omse_fw())
        .then(SomeEnum::b_case_fw())
        .then(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);
    }
}

Run it yourself:

cargo run --example box_keypath

🌟 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: Only 1.43x overhead for reads, 98.3x faster when reused!

⚡ Performance

KeyPaths are optimized for performance with minimal overhead. Below are benchmark results comparing direct unwrap vs keypaths for different operations (all times in picoseconds):

Operation Direct Unwrap KeyPath Overhead Notes
Read (3 levels) 379.28 ps 820.81 ps 2.16x ~441 ps absolute difference
Write (3 levels) 377.04 ps 831.65 ps 2.21x ~454 ps absolute difference
Deep Read (5 levels, no enum) 379.37 ps 926.83 ps 2.44x Pure Option chain
Deep Read (5 levels, with enum) 384.10 ps 1,265.3 ps 3.29x Includes enum case path + Box adapter
Write (5 levels, with enum) 385.23 ps 1,099.7 ps 2.85x Writable with enum case path
Keypath Creation N/A 325.60 ps N/A One-time cost, negligible
Reused Read (100x) 36,808 ps 36,882 ps 1.00x Near-zero overhead when reused!
Pre-composed N/A 848.26 ps N/A 1.45x faster than on-the-fly
Composed on-the-fly N/A 1,234.0 ps N/A Composition overhead

Key Findings:

  • Reused keypaths have near-zero overhead (1.00x vs baseline)
  • Pre-composition provides 1.45x speedup over on-the-fly composition
  • Write operations show similar overhead to reads (2.21x vs 2.16x)
  • Deep nesting with enums has higher overhead (3.29x) but remains manageable
  • ✅ Single-use overhead is minimal (~400-500 ps for typical operations)

Best Practices:

  • Pre-compose keypaths before loops/iterations (1.45x faster)
  • Reuse keypaths whenever possible (near-zero overhead)
  • Single-use overhead is negligible (< 1 ns for reads)
  • Deep nested paths with enums have higher overhead but still manageable

See benches/BENCHMARK_SUMMARY.md for detailed performance analysis.


🛠 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)
  • [] Derive macros for complex multi-field enum variants

📜 License

  • Mozilla Public License 2.0