🔑 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
[]
= "1.0.2"
= "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
[]
= "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
📦 Installation
Recommended: Static Dispatch (rust-keypaths)
[]
= "1.0.0"
= "1.0.0"
Legacy: Dynamic Dispatch (key-paths-core)
[]
= "1.6.0" # Use 1.6.0 for dynamic dispatch
= "1.1.0"
API Differences
rust-keypaths (Static Dispatch):
use ;
let kp = new;
let opt_kp = new;
let writable_kp = new;
key-paths-core (Dynamic Dispatch):
use KeyPaths;
let kp = readable;
let opt_kp = failable_readable;
let writable_kp = writable;
🚀 Examples
Deep Nested Composition with Box and Enums
This example demonstrates keypath composition through deeply nested structures with Box<T> and enum variants:
use ;
Run it yourself:
🌟 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