🔑 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
[]
= "1.4.0"
= "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>>andArc<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 ;
parking_lot Support (Default for Mutex/RwLock)
⚠️ IMPORTANT: When using the derive macro,
MutexandRwLockdefault toparking_lotunless you explicitly usestd::sync::Mutexorstd::sync::RwLock.
[]
= { = "1.4.0", = ["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 Arc;
use RwLock;
use Keypaths;
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: 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
- ✅ 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
🛠 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