🔑 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.
rust-keypaths + keypaths-proc (Recommended)
- ✅ Faster performance, better compiler optimizations
- ✅ Write operations can be faster than manual unwrapping at deeper nesting levels
- ✅ Zero runtime overhead
- ✅ 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
- ✅ Tokio support - Async keypath chains through
Arc<tokio::sync::Mutex<T>>andArc<tokio::sync::RwLock<T>>
[]
= "1.7.0"
= "1.7.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
- ✅ Tokio support - Async keypath chains through
Arc<tokio::sync::Mutex<T>>andArc<tokio::sync::RwLock<T>> - ✅ Compile-time type safety - Invalid keypath compositions fail at compile time, preventing runtime errors
🚀 Examples
Deep Nested Composition with Box and Enums
This example demonstrates keypath composition through deeply nested structures with Box<T> and enum variants:
use ;
Type Safety: Compile-Time Error Prevention
Keypaths provide compile-time type safety - if you try to compose keypaths that don't share the same root type, the compiler will catch the error before your code runs.
The Rule: When chaining keypaths with .then(), the Value type of the first keypath must match the Root type of the second keypath.
use Keypaths;
What happens:
- ✅ Valid compositions compile successfully
- ❌ Invalid compositions fail at compile time with clear error messages
- 🛡️ No runtime errors - type mismatches are caught before execution
- 📝 Clear error messages - Rust compiler shows exactly what types are expected vs. found
This ensures that keypath chains are always type-safe and prevents bugs that would only be discovered at runtime.
Running the example:
parking_lot Support (Default for Mutex/RwLock)
⚠️ IMPORTANT: When using the derive macro,
MutexandRwLockdefault toparking_lotunless you explicitly usestd::sync::Mutexorstd::sync::RwLock.
[]
= { = "1.7.0", = ["parking_lot"] }
= "1.7.0"
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:
Tokio Support (Async Locks)
⚠️ IMPORTANT: Tokio support requires the
tokiofeature and usestokio::sync::Mutexandtokio::sync::RwLock. All operations are async and must be awaited.
[]
= { = "1.7.0", = ["tokio"] }
= "1.7.0"
= { = "1.38.0", = ["sync", "rt", "rt-multi-thread", "macros"] }
Derive Macro Generated Methods for Tokio Locks
The derive macro generates helper methods for Arc<tokio::sync::Mutex<T>> and Arc<tokio::sync::RwLock<T>> fields:
| Field Type | Generated Methods | Description |
|---|---|---|
Arc<tokio::sync::Mutex<T>> |
_r(), _w(), _fr_at(kp), _fw_at(kp) |
Chain through tokio::sync::Mutex (async) |
Arc<tokio::sync::RwLock<T>> |
_r(), _w(), _fr_at(kp), _fw_at(kp) |
Chain through tokio::sync::RwLock (async, read/write locks) |
use Arc;
use ;
use Keypaths;
// Generate all methods (readable, writable, owned)
async
Key features:
- ✅ Async operations: All lock operations are async and must be awaited
- ✅ Read/write locks:
RwLocksupports concurrent reads with_fr_at()and exclusive writes with_fw_at() - ✅ Optional chaining: Works seamlessly with
Option<Arc<tokio::sync::Mutex<T>>>andOption<Arc<tokio::sync::RwLock<T>>> - ✅ Nested composition: Chain through multiple levels of Tokio locks and nested structures
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.
Benchmarking RwLock Operations
The library includes comprehensive benchmarks for both parking_lot::RwLock and tokio::sync::RwLock operations:
parking_lot::RwLock benchmarks:
Tokio RwLock benchmarks (read and write):
The benchmarks compare:
- ✅ Keypath approach: Using
_fr_at()and_fw_at()methods for readable and writable access - ⚙️ Traditional approach: Manual read/write guards with nested field access
Benchmarks include:
- Deeply nested read/write operations through
Arc<RwLock<T>> - Optional field access (
Option<T>) - Multiple sequential operations
- Both synchronous (
parking_lot) and asynchronous (tokio) primitives
Benchmark Results:
| Operation | Keypath | Manual Guard | Overhead | Notes |
|---|---|---|---|---|
| parking_lot::RwLock - Deep Write | 24.5 ns | 23.9 ns | 2.5% slower | Deeply nested write through Arc<RwLock<T>> |
| parking_lot::RwLock - Simple Write | 8.5 ns | 8.6 ns | 1.2% faster ⚡ | Simple field write (Option<i32>) |
| parking_lot::RwLock - Field Write | 23.8 ns | 23.9 ns | 0.4% faster ⚡ | Field write (Option<String>) |
| parking_lot::RwLock - Multiple Writes | 55.8 ns | 41.8 ns | 33.5% slower | Multiple sequential writes (single guard faster) |
| tokio::sync::RwLock - Deep Read | 104.8 ns | 104.6 ns | 0.2% slower | Deeply nested async read |
| tokio::sync::RwLock - Deep Write | 124.8 ns | 124.1 ns | 0.6% slower | Deeply nested async write |
| tokio::sync::RwLock - Simple Write | 103.8 ns | 105.0 ns | 1.2% faster ⚡ | Simple async field write |
| tokio::sync::RwLock - Field Read | 103.3 ns | 103.2 ns | 0.1% slower | Simple async field read |
| tokio::sync::RwLock - Field Write | 125.7 ns | 124.6 ns | 0.9% slower | Simple async field write |
Key findings:
- ✅ parking_lot::RwLock: Keypaths show essentially identical performance (0-2.5% overhead) for single operations
- ✅ tokio::sync::RwLock: Keypaths show essentially identical performance (0-1% overhead) for async operations
- ⚡ Simple operations: Keypaths can be faster than manual guards in some cases (1-2% improvement)
- ⚠️ Multiple writes: Manual single guard is faster (33% overhead) - use single guard for multiple operations
- 🎯 Type safety: Minimal performance cost for significant type safety and composability benefits
Detailed Analysis:
- For detailed performance analysis, see
benches/BENCHMARK_SUMMARY.md - For performance optimization details, see
benches/PERFORMANCE_ANALYSIS.md - For complete benchmark results, see
benches/BENCHMARK_RESULTS.md
🔄 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 | ✅ | ⚠️ 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: 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 - Tokio support for async keypath chains through
Arc<tokio::sync::Mutex<T>>andArc<tokio::sync::RwLock<T>> - Derive macros for complex multi-field enum variants
📜 License
- Mozilla Public License 2.0