🔑 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.
✨ 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
[]
= "1.7.0"
= "1.1.0"
🎯 Choose Your Macro
#[derive(Keypath)] - Simple & Beginner-Friendly
- One method per field:
field_name() - Smart keypath selection: Automatically chooses readable or failable readable based on field type
- No option chaining: Perfect for beginners and simple use cases
- Clean API: Just call
Struct::field_name()and you're done!
use Keypath;
// Usage
let user = User ;
let name_keypath = name;
let email_keypath = email;
let name = name_keypath.get; // Some("Alice")
let email = email_keypath.get; // Some("alice@example.com")
#[derive(Keypaths)] - Advanced & Feature-Rich
- Multiple methods per field:
field_r(),field_w(),field_fr(),field_fw(),field_o(),field_fo() - Full control: Choose exactly which type of keypath you need
- Option chaining: Perfect for intermediate and advanced developers
- Comprehensive: Supports all container types and access patterns
use Keypaths;
// Default scope for every field is Readable, others Writable, Owned and All.
// Usage - you choose the exact method
let user = User ;
let name_keypath = name_r;
let email_keypath = email_fr;
let name = name_keypath.get; // Some("Alice") - readable
let email = email_keypath.get; // Some("alice@example.com") - failable readable
Widely used - Deeply nested struct
use ;
// Default scope for every field is Readable, others Writable, Owned and All.
// Default scope for every field is Readable, others Writable, Owned and All.
// Default scope for every field is Readable, others Writable, Owned and All.
// Default scope for every field is Readable, others Writable, Owned and All.
Recommendation: Start with #[derive(Keypath)] for simplicity, upgrade to #[derive(Keypaths)] when you need more control!
Keypath vs Keypaths - When to Use Which?
| Feature | #[derive(Keypath)] |
#[derive(Keypaths)] |
|---|---|---|
| API Complexity | Simple - one method per field | Advanced - multiple methods per field |
| Learning Curve | Beginner-friendly | Requires understanding of keypath types |
| Container Support | Basic containers only | Full container support including Result, Mutex, RwLock, Wea****k |
| Option Chaining | No - smart selection only | Yes - full control over failable vs non-failable |
| Writable Access | Limited | Full writable support |
| Use Case | Simple field access, beginners | Complex compositions, advanced users |
When to use Keypath:
- You're new to keypaths
- You want simple, clean field access
- You don't need complex option chaining
- You're working with basic types
When to use Keypaths:
- You need full control over keypath types
- You're composing complex nested structures
- You need writable access to fields
- You're working with advanced container types
🚀 Examples
See examples/ for many runnable samples. Below are a few highlights.
Quick Start - Simple Keypaths Usage
use Keypath;
Attribute-Scoped Generation (NEW!)
Struct-level and field-level attributes let you control which keypath methods are emitted. The default scope is Readable, but you can opt into Writable, Owned, or All on individual fields or the entire type.
use KeyPaths;
use Keypaths;
// default scope for every field
Run it yourself:
cargo run --example attribute_scopes
📦 Container Adapters & References (NEW!)
KeyPaths now support smart pointers, containers, and references via adapter methods:
Smart Pointer Adapters
Use .for_arc(), .for_box(), or .for_rc() to adapt keypaths for wrapped types:
use Keypaths;
use Arc;
let products: = vec!;
// Adapt keypath to work with Arc<Product>
let price_path = price.for_arc;
let affordable: = products
.iter
.filter
.collect;
Reference Support
Use .get_ref() and .get_mut_ref() for collections of references:
use Keypaths;
let products: = hashmap.values.collect;
let price_path = price;
for product_ref in &products
Supported Adapters:
.for_arc()- Works withArc<T>(read-only).for_box()- Works withBox<T>(read & write).for_rc()- Works withRc<T>(read-only).get_ref()- Works with&Treferences.get_mut_ref()- Works with&mut Treferences
Examples:
examples/container_adapters.rs- Smart pointer usageexamples/reference_keypaths.rs- Reference collectionskey-paths-core/examples/container_adapter_test.rs- Test suite
Documentation: See CONTAINER_ADAPTERS.md and REFERENCE_SUPPORT.md
🌟 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:
| Operation | Overhead | Notes |
|---|---|---|
| Read (3 levels) | 1.43x (43% slower) | Only ~170 ps absolute difference |
| Write (3 levels) | 10.8x slower | ~3.8 ns absolute difference |
| Reused Read | 98.3x faster ⚡ | Primary benefit - reuse keypaths! |
| Pre-composed | Optimal | 390x faster than on-the-fly composition |
Key Optimizations Applied:
- ✅ Direct
matchcomposition (Phase 1) - eliminatedand_thenoverhead - ✅
Rcinstead ofArc- faster for single-threaded use - ✅ Aggressive inlining -
#[inline(always)]on hot paths
Best Practices:
- Pre-compose keypaths before loops/iterations
- Reuse keypaths whenever possible to get the 98x speedup
- Single-use overhead is negligible (< 1 ns for reads)
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