🔑 KeyPaths 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
- ✅ Functional chains for
Arc<Mutex<T>>/Arc<RwLock<T>>- Compose keypaths through sync primitives - ✅ parking_lot support - Optional feature for faster locks
[]
= "1.1.0"
= "1.1.0"
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
[]
= "1.6.0" # Use 1.6.0 for dynamic dispatch
= "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 - ✅ Functional chains for
Arc<Mutex<T>>andArc<RwLock<T>>- Compose-first, apply-later pattern - ✅ parking_lot support - Feature-gated support for faster synchronization primitives
📦 Installation
Recommended: Static Dispatch (rust-keypaths)
[]
= "1.1.0"
= "1.1.0"
With parking_lot Support
[]
= { = "1.1.0", = ["parking_lot"] }
= "1.1.0"
API Differences
rust-keypaths (Static Dispatch):
use ;
let kp = new;
let opt_kp = new;
let writable_kp = new;
🚀 Examples
Deep Nested Composition with Box and Enums
This example demonstrates keypath composition through deeply nested structures with Box<T> and enum variants:
use ;
Functional Chains for Arc<Mutex> and Arc<RwLock>
Compose keypaths through synchronization primitives with a functional, compose-first approach:
use ;
use ;
Available chain methods:
| Method | Description |
|---|---|
chain_arc_mutex(keypath) |
Read through Arc<Mutex<T>> |
chain_arc_mutex_optional(keypath) |
Optional read through Arc<Mutex<T>> |
chain_arc_mutex_writable(keypath) |
Write through Arc<Mutex<T>> |
chain_arc_mutex_writable_optional(keypath) |
Optional write through Arc<Mutex<T>> |
chain_arc_rwlock(keypath) |
Read through Arc<RwLock<T>> (read lock) |
chain_arc_rwlock_optional(keypath) |
Optional read through Arc<RwLock<T>> |
chain_arc_rwlock_writable(keypath) |
Write through Arc<RwLock<T>> (write lock) |
chain_arc_rwlock_writable_optional(keypath) |
Optional write through Arc<RwLock<T>> |
Running the example:
parking_lot Support
For faster synchronization with parking_lot::Mutex and parking_lot::RwLock:
[]
= { = "1.1.0", = ["parking_lot"] }
use Arc;
use ;
// Chain methods for parking_lot (feature-gated)
parking_mutex_data_r
.chain_arc_parking_mutex
.get;
parking_rwlock_data_r
.chain_arc_parking_rwlock_writable
.get_mut;
Key advantage: parking_lot locks never fail (no poisoning), so chain methods don't return Option for the lock operation itself.
Running the example:
🌟 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.46x overhead for reads, 93.6x faster when reused, and 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
Limitations of lens-rs, pl-lens, and keypath
Both lens-rs, pl-lens (Plausible Labs), and keypath have several limitations when working with Rust's type system, especially for nested structures:
keypath limitations:
- ❌ No enum variant support: No built-in support for enum case paths (prisms)
- ❌ No Option chain support: Requires manual
.and_then()composition for Option types - ❌ Limited container support: No built-in support for
Result<T, E>,Mutex<T>,RwLock<T>, or collection types - ❌ No failable keypaths: Cannot easily compose through Option chains with built-in methods
- ❌ No writable failable keypaths: Missing support for composing writable access through Option chains
- ❌ Limited composition API: Less ergonomic composition compared to
.then()chaining - ⚠️ Maintenance status: May have limited active maintenance
pl-lens limitations:
- ❌ No support for
Option<Struct>nested compositions: The#[derive(Lenses)]macro fails to generate proper lens types for nested structs wrapped inOption<T>, requiring manual workarounds - ❌ Limited enum support: No built-in support for enum variant case paths (prisms)
- ❌ No automatic failable composition: Requires manual composition through
.and_then()chains for Option types - ❌ Limited container support: No built-in support for
Result<T, E>,Mutex<T>,RwLock<T>, or collection types - ❌ Named fields only: The derive macro only works with structs that have named fields, not tuple structs
- ❌ No writable failable keypaths: Cannot compose writable access through Option chains easily
- ❌ Type system limitations: The lens composition through Option types requires manual function composition, losing type safety
lens-rs limitations:
- ❌ Different API design: Uses a different lens abstraction that doesn't match Rust's ownership model as well
- ❌ Limited ecosystem: Less mature and fewer examples/documentation
- ❌ Composition complexity: More verbose composition syntax
Feature Comparison Table
| 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
- ✅ Native Option support: Built-in failable keypaths (
_fr/_fw) that compose seamlessly throughOption<T>chains (unlike keypath, pl-lens, and lens-rs which require manual composition) - ✅ Enum CasePaths: First-class support for enum variant access (prisms) with
#[derive(Casepaths)](unique feature not found in keypath, pl-lens, or lens-rs) - ✅ Container types: Built-in support for
Result,Mutex,RwLock,Arc,Rc,Box, and all standard collections (comprehensive container support unmatched by alternatives) - ✅ Functional chains for sync primitives: Compose keypaths through
Arc<Mutex<T>>andArc<RwLock<T>>with a clean, functional API - ✅ parking_lot support: Feature-gated support for faster
parking_lot::Mutexandparking_lot::RwLock - ✅ Zero-cost abstractions: Static dispatch with minimal overhead (1.46x for reads, near-zero for writes) - benchmarked and optimized
- ✅ Comprehensive derive macros: Automatic generation for structs (named and tuple), enums, and all container types
- ✅ Swift-inspired API: Familiar API for developers coming from Swift's KeyPath system with
.then()composition - ✅ Deep composition: Works seamlessly with 10+ levels of nesting without workarounds (tested and verified)
- ✅ Type-safe composition: Full compile-time type checking with
.then()method - ✅ Active development: Regularly maintained with comprehensive feature set and documentation
Example: Why rust-keypaths is Better for Nested Option Chains
pl-lens approach (requires manual work):
// Manual composition - verbose and error-prone
let result = struct_instance
.level1_field
.as_ref
.and_then
.and_then
// ... continues for 10 levels
rust-keypaths approach (composable and type-safe):
// Clean composition - type-safe and reusable
let keypath = level1_field_fr
.then
.then
// ... continues for 10 levels
.then;
let result = keypath.get; // Reusable, type-safe, fast
🛠 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>>andArc<RwLock<T>> -
parking_lotsupport for faster synchronization primitives - Derive macros for complex multi-field enum variants
📜 License
- Mozilla Public License 2.0