30 releases (stable)
Uses new Rust 2024
| new 1.11.0 | Dec 7, 2025 |
|---|---|
| 1.10.0 | Nov 23, 2025 |
| 1.6.0 | Oct 26, 2025 |
| 0.9.0 | Sep 20, 2025 |
#351 in Rust patterns
17KB
🔑 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 + Syncbounds 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:
- 🔍 rust-queries-builder - Type-safe, SQL-like queries for in-memory collections
- 🎭 rust-overture - Functional programming utilities and abstractions
- 🚀 rust-prelude-plus - Enhanced prelude with additional utilities and traits
🔗 Helpful Links & Resources
- 📘 type-safe property paths
- 📘 Swift KeyPath documentation
- 📘 Elm Architecture & Functional Lenses
- 📘 Rust Macros Book
- 📘 Category Theory in FP (for intuition)
💡 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
Dependencies
~0.3–0.9MB
~19K SLoC