| Crates.io | rust-keypaths |
| lib.rs | rust-keypaths |
| version | 1.9.0 |
| created_at | 2025-12-06 12:53:39.875093+00 |
| updated_at | 2026-01-25 15:04:42.751824+00 |
| description | A static dispatch, faster alternative to rust-key-paths - Type-safe, composable keypaths for Rust with superior performance |
| homepage | |
| repository | https://github.com/akashsoni01/rust-key-paths |
| max_upload_size | |
| id | 1970126 |
| size | 447,267 |
A faster alternative to key-paths-core - A lightweight, zero-cost abstraction library for safe, composable access to nested data structures in Rust. Inspired by Functional lenses and Swift's KeyPath system, this library provides type-safe keypaths for struct fields and enum variants for superior performance.
This is a faster alternative to the rust-key-paths library :
KeyPath<Root, Value, F> - Readable keypath for direct field accessOptionalKeyPath<Root, Value, F> - Failable keypath for Option<T> chainsWritableKeyPath<Root, Value, F> - Writable keypath for mutable field accessWritableOptionalKeyPath<Root, Value, F> - Failable writable keypath for mutable Option<T> chainsEnumKeyPaths - Static factory for enum variant extraction and container unwrapping.then() for nested accessBox<T>, Arc<T>, Rc<T>, Option<T>Add to your Cargo.toml:
[dependencies]
rust-keypaths = { path = "../rust-keypaths" }
# Optional features
[features]
tagged = ["rust-keypaths/tagged"] # Enable tagged-core support
parking_lot = ["rust-keypaths/parking_lot"] # Enable parking_lot support
use rust_keypaths::{KeyPath, OptionalKeyPath};
#[derive(Debug)]
struct User {
name: String,
email: Option<String>,
}
let user = User {
name: "Akash".to_string(),
email: Some("akash@example.com".to_string()),
};
// Create readable keypath
let name_kp = KeyPath::new(|u: &User| &u.name);
println!("Name: {}", name_kp.get(&user));
// Create failable keypath for Option
let email_kp = OptionalKeyPath::new(|u: &User| u.email.as_ref());
if let Some(email) = email_kp.get(&user) {
println!("Email: {}", email);
}
#[derive(Debug)]
struct Address {
street: String,
}
#[derive(Debug)]
struct Profile {
address: Option<Address>,
}
#[derive(Debug)]
struct User {
profile: Option<Profile>,
}
let user = User {
profile: Some(Profile {
address: Some(Address {
street: "123 Main St".to_string(),
}),
}),
};
// Chain keypaths to access nested field
let street_kp = OptionalKeyPath::new(|u: &User| u.profile.as_ref())
.then(OptionalKeyPath::new(|p: &Profile| p.address.as_ref()))
.then(OptionalKeyPath::new(|a: &Address| Some(&a.street)));
if let Some(street) = street_kp.get(&user) {
println!("Street: {}", street);
}
use rust_keypaths::{OptionalKeyPath, KeyPath};
struct Container {
boxed: Option<Box<String>>,
}
let container = Container {
boxed: Some(Box::new("Hello".to_string())),
};
// Unwrap Option<Box<String>> to Option<&String> automatically
let kp = OptionalKeyPath::new(|c: &Container| c.boxed.as_ref())
.for_box(); // Type automatically inferred!
if let Some(value) = kp.get(&container) {
println!("Value: {}", value);
}
Compose keypaths through synchronization primitives with a functional, compose-first approach:
use std::sync::{Arc, Mutex, RwLock};
use keypaths_proc::{Kp, WritableKeypaths};
#[derive(Debug, Kp, WritableKeypaths)]
struct Container {
mutex_data: Arc<Mutex<DataStruct>>,
rwlock_data: Arc<RwLock<DataStruct>>,
}
#[derive(Debug, Kp, WritableKeypaths)]
struct DataStruct {
name: String,
optional_value: Option<String>,
}
fn main() {
let container = Container::new();
// Read through Arc<Mutex<T>> - compose the chain, then apply
Container::mutex_data_r()
.chain_arc_mutex_at_kp(DataStruct::name_r())
.get(&container, |value| {
println!("Name: {}", value);
});
// Write through Arc<Mutex<T>>
Container::mutex_data_r()
.chain_arc_mutex_writable_at_kp(DataStruct::name_w())
.get_mut(&container, |value| {
*value = "New name".to_string();
});
// Read through Arc<RwLock<T>> (read lock)
Container::rwlock_data_r()
.chain_arc_rwlock_at_kp(DataStruct::name_r())
.get(&container, |value| {
println!("Name: {}", value);
});
// Write through Arc<RwLock<T>> (write lock)
Container::rwlock_data_r()
.chain_arc_rwlock_writable_at_kp(DataStruct::name_w())
.get_mut(&container, |value| {
*value = "New name".to_string();
});
}
Running the example:
cargo run --example readable_keypaths_new_containers_test
The library provides utilities for accessing elements in various collection types:
use rust_keypaths::{OptionalKeyPath, containers};
use std::collections::{HashMap, VecDeque, HashSet, BinaryHeap};
struct Data {
vec: Vec<String>,
map: HashMap<String, i32>,
deque: VecDeque<String>,
set: HashSet<String>,
heap: BinaryHeap<String>,
}
let data = Data { /* ... */ };
// Access Vec element at index
let vec_kp = OptionalKeyPath::new(|d: &Data| Some(&d.vec))
.then(containers::for_vec_index::<String>(1));
// Access HashMap value by key
let map_kp = OptionalKeyPath::new(|d: &Data| Some(&d.map))
.then(containers::for_hashmap_key("key1".to_string()));
// Access VecDeque element at index
let deque_kp = OptionalKeyPath::new(|d: &Data| Some(&d.deque))
.then(containers::for_vecdeque_index::<String>(0));
// Access HashSet element
let set_kp = OptionalKeyPath::new(|d: &Data| Some(&d.set))
.then(containers::for_hashset_get("value".to_string()));
// Peek at BinaryHeap top element
let heap_kp = OptionalKeyPath::new(|d: &Data| Some(&d.heap))
.then(containers::for_binaryheap_peek::<String>());
Supported Collections:
Vec<T> - Indexed access via for_vec_index(index)VecDeque<T> - Indexed access via for_vecdeque_index(index)LinkedList<T> - Indexed access via for_linkedlist_index(index)HashMap<K, V> - Key-based access via for_hashmap_key(key)BTreeMap<K, V> - Key-based access via for_btreemap_key(key)HashSet<T> - Element access via for_hashset_get(value)BTreeSet<T> - Element access via for_btreeset_get(value)BinaryHeap<T> - Peek access via for_binaryheap_peek()use rust_keypaths::{OptionalKeyPath, EnumKeyPaths};
enum Result {
Ok(String),
Err(String),
}
let result = Result::Ok("success".to_string());
// Extract from enum variant
let ok_kp = EnumKeyPaths::for_variant(|r: &Result| {
if let Result::Ok(value) = r {
Some(value)
} else {
None
}
});
if let Some(value) = ok_kp.get(&result) {
println!("Success: {}", value);
}
new(getter: F) -> Self - Create a new keypath from a getter functionget(&self, root: &Root) -> &Value - Get a reference to the valuefor_box<Target>(self) -> KeyPath<Root, Target, ...> - Unwrap Box<T> to T (type inferred)for_arc<Target>(self) -> KeyPath<Root, Target, ...> - Unwrap Arc<T> to T (type inferred)for_rc<Target>(self) -> KeyPath<Root, Target, ...> - Unwrap Rc<T> to T (type inferred)let kp = KeyPath::new(|b: &Box<String>| b.as_ref());
let unwrapped = kp.for_box(); // Automatically infers String
new(getter: F) -> Self - Create a new optional keypathget(&self, root: &Root) -> Option<&Value> - Get an optional referencethen<SubValue, G>(self, next: OptionalKeyPath<Value, SubValue, G>) -> OptionalKeyPath<Root, SubValue, ...> - Chain keypathsfor_box<Target>(self) -> OptionalKeyPath<Root, Target, ...> - Unwrap Option<Box<T>> to Option<&T>for_arc<Target>(self) -> OptionalKeyPath<Root, Target, ...> - Unwrap Option<Arc<T>> to Option<&T>for_rc<Target>(self) -> OptionalKeyPath<Root, Target, ...> - Unwrap Option<Rc<T>> to Option<&T>for_option<T>() -> OptionalKeyPath<Option<T>, T, ...> - Static method to create keypath for Option<T>let kp1 = OptionalKeyPath::new(|s: &Struct| s.field.as_ref());
let kp2 = OptionalKeyPath::new(|o: &Other| o.value.as_ref());
let chained = kp1.then(kp2); // Chain them together
new(getter: F) -> Self - Create a new writable keypath from a getter functionget_mut(&self, root: &mut Root) -> &mut Value - Get a mutable reference to the valuefor_box<Target>(self) -> WritableKeyPath<Root, Target, ...> - Unwrap Box<T> to T (mutable, type inferred)for_arc<Target>(self) -> WritableKeyPath<Root, Target, ...> - Unwrap Arc<T> to T (mutable, type inferred)for_rc<Target>(self) -> WritableKeyPath<Root, Target, ...> - Unwrap Rc<T> to T (mutable, type inferred)let mut data = MyStruct { field: "value".to_string() };
let kp = WritableKeyPath::new(|s: &mut MyStruct| &mut s.field);
*kp.get_mut(&mut data) = "new_value".to_string();
new(getter: F) -> Self - Create a new writable optional keypathget_mut(&self, root: &mut Root) -> Option<&mut Value> - Get an optional mutable referencethen<SubValue, G>(self, next: WritableOptionalKeyPath<Value, SubValue, G>) -> WritableOptionalKeyPath<Root, SubValue, ...> - Chain writable keypathsfor_box<Target>(self) -> WritableOptionalKeyPath<Root, Target, ...> - Unwrap Option<Box<T>> to Option<&mut T>for_arc<Target>(self) -> WritableOptionalKeyPath<Root, Target, ...> - Unwrap Option<Arc<T>> to Option<&mut T>for_rc<Target>(self) -> WritableOptionalKeyPath<Root, Target, ...> - Unwrap Option<Rc<T>> to Option<&mut T>for_option<T>() -> WritableOptionalKeyPath<Option<T>, T, ...> - Static method to create writable keypath for Option<T>let mut data = MyStruct { field: Some("value".to_string()) };
let kp = WritableOptionalKeyPath::new(|s: &mut MyStruct| s.field.as_mut());
if let Some(value) = kp.get_mut(&mut data) {
*value = "new_value".to_string();
}
for_variant<Enum, Variant, ExtractFn>(extractor: ExtractFn) -> OptionalKeyPath<Enum, Variant, ...> - Extract from enum variantfor_match<Enum, Output, MatchFn>(matcher: MatchFn) -> KeyPath<Enum, Output, ...> - Match against multiple variantsfor_ok<T, E>() -> OptionalKeyPath<Result<T, E>, T, ...> - Extract Ok from Resultfor_err<T, E>() -> OptionalKeyPath<Result<T, E>, E, ...> - Extract Err from Resultfor_some<T>() -> OptionalKeyPath<Option<T>, T, ...> - Extract from Option<T>for_option<T>() -> OptionalKeyPath<Option<T>, T, ...> - Alias for for_somefor_box<T>() -> KeyPath<Box<T>, T, ...> - Create keypath for Box<T>for_arc<T>() -> KeyPath<Arc<T>, T, ...> - Create keypath for Arc<T>for_rc<T>() -> KeyPath<Rc<T>, T, ...> - Create keypath for Rc<T>for_box_mut<T>() -> WritableKeyPath<Box<T>, T, ...> - Create writable keypath for Box<T>// Extract from Result
let ok_kp = EnumKeyPaths::for_ok::<String, String>();
let result: Result<String, String> = Ok("value".to_string());
if let Some(value) = ok_kp.get(&result) {
println!("{}", value);
}
Utility functions for accessing elements in standard library collections.
for_vec_index<T>(index: usize) -> OptionalKeyPath<Vec<T>, T, ...> - Access element at index in Vec<T>for_vecdeque_index<T>(index: usize) -> OptionalKeyPath<VecDeque<T>, T, ...> - Access element at index in VecDeque<T>for_linkedlist_index<T>(index: usize) -> OptionalKeyPath<LinkedList<T>, T, ...> - Access element at index in LinkedList<T>for_hashmap_key<K, V>(key: K) -> OptionalKeyPath<HashMap<K, V>, V, ...> - Access value by key in HashMap<K, V>for_btreemap_key<K, V>(key: K) -> OptionalKeyPath<BTreeMap<K, V>, V, ...> - Access value by key in BTreeMap<K, V>for_hashset_get<T>(value: T) -> OptionalKeyPath<HashSet<T>, T, ...> - Get element from HashSet<T>for_btreeset_get<T>(value: T) -> OptionalKeyPath<BTreeSet<T>, T, ...> - Get element from BTreeSet<T>for_binaryheap_peek<T>() -> OptionalKeyPath<BinaryHeap<T>, T, ...> - Peek at top element in BinaryHeap<T>for_vec_index_mut<T>(index: usize) -> WritableOptionalKeyPath<Vec<T>, T, ...> - Mutate element at index in Vec<T>for_vecdeque_index_mut<T>(index: usize) -> WritableOptionalKeyPath<VecDeque<T>, T, ...> - Mutate element at index in VecDeque<T>for_linkedlist_index_mut<T>(index: usize) -> WritableOptionalKeyPath<LinkedList<T>, T, ...> - Mutate element at index in LinkedList<T>for_hashmap_key_mut<K, V>(key: K) -> WritableOptionalKeyPath<HashMap<K, V>, V, ...> - Mutate value by key in HashMap<K, V>for_btreemap_key_mut<K, V>(key: K) -> WritableOptionalKeyPath<BTreeMap<K, V>, V, ...> - Mutate value by key in BTreeMap<K, V>for_hashset_get_mut<T>(value: T) -> WritableOptionalKeyPath<HashSet<T>, T, ...> - Limited mutable access (see limitations)for_btreeset_get_mut<T>(value: T) -> WritableOptionalKeyPath<BTreeSet<T>, T, ...> - Limited mutable access (see limitations)for_binaryheap_peek_mut<T>() -> WritableOptionalKeyPath<BinaryHeap<T>, T, ...> - Limited mutable access (see limitations)Note: Mutex and RwLock return guards that own the lock, not references. These helper functions are provided for convenience, but direct lock(), read(), and write() calls are recommended for better control.
lock_mutex<T>(mutex: &Mutex<T>) -> Option<MutexGuard<T>> - Lock a Mutex<T> and return guardread_rwlock<T>(rwlock: &RwLock<T>) -> Option<RwLockReadGuard<T>> - Read-lock an RwLock<T> and return guardwrite_rwlock<T>(rwlock: &RwLock<T>) -> Option<RwLockWriteGuard<T>> - Write-lock an RwLock<T> and return guardlock_arc_mutex<T>(arc_mutex: &Arc<Mutex<T>>) -> Option<MutexGuard<T>> - Lock an Arc<Mutex<T>> and return guardread_arc_rwlock<T>(arc_rwlock: &Arc<RwLock<T>>) -> Option<RwLockReadGuard<T>> - Read-lock an Arc<RwLock<T>> and return guardwrite_arc_rwlock<T>(arc_rwlock: &Arc<RwLock<T>>) -> Option<RwLockWriteGuard<T>> - Write-lock an Arc<RwLock<T>> and return guardupgrade_weak<T>(weak: &Weak<T>) -> Option<Arc<T>> - Upgrade a Weak<T> to Arc<T>upgrade_rc_weak<T>(weak: &Rc::Weak<T>) -> Option<Rc<T>> - Upgrade an Rc::Weak<T> to Rc<T>When the parking_lot feature is enabled:
lock_parking_mutex<T>(mutex: &parking_lot::Mutex<T>) -> MutexGuard<T> - Lock a parking_lot Mutex<T>read_parking_rwlock<T>(rwlock: &parking_lot::RwLock<T>) -> RwLockReadGuard<T> - Read-lock a parking_lot RwLock<T>write_parking_rwlock<T>(rwlock: &parking_lot::RwLock<T>) -> RwLockWriteGuard<T> - Write-lock a parking_lot RwLock<T>When the tagged feature is enabled:
for_tagged<Tag, T>() -> KeyPath<Tagged<Tag, T>, T, ...> - Access inner value of Tagged<Tag, T> (requires Deref)for_tagged_mut<Tag, T>() -> WritableKeyPath<Tagged<Tag, T>, T, ...> - Mutably access inner value of Tagged<Tag, T> (requires DerefMut)use rust_keypaths::containers;
let vec = vec!["a", "b", "c"];
let vec_kp = containers::for_vec_index::<&str>(1);
if let Some(value) = vec_kp.get(&vec) {
println!("{}", value); // prints "b"
}
// Using locks
use std::sync::Mutex;
let mutex = Mutex::new(42);
if let Some(guard) = containers::lock_mutex(&mutex) {
println!("Mutex value: {}", *guard);
}
use rust_keypaths::{OptionalKeyPath, EnumKeyPaths};
// 7 levels deep: Root -> Option -> Option -> Option -> Enum -> Option -> Option -> Box<String>
let chained_kp = OptionalKeyPath::new(|r: &Root| r.level1.as_ref())
.then(OptionalKeyPath::new(|l1: &Level1| l1.level2.as_ref()))
.then(OptionalKeyPath::new(|l2: &Level2| l2.level3.as_ref()))
.then(EnumKeyPaths::for_variant(|e: &Enum| {
if let Enum::Variant(v) = e { Some(v) } else { None }
}))
.then(OptionalKeyPath::new(|v: &Variant| v.level4.as_ref()))
.then(OptionalKeyPath::new(|l4: &Level4| l4.level5.as_ref()))
.for_box(); // Automatically unwraps Box<String> to &String
All benchmarks compare keypath access vs manual unwrapping on deeply nested structures.
| Method | Time | Overhead |
|---|---|---|
| Keypath | ~855 ps | 2.24x |
| Manual Unwrap | ~382 ps | 1.0x (baseline) |
Overhead: ~473 ps (2.24x slower than manual unwrapping)
| Method | Time | Overhead |
|---|---|---|
| Keypath | ~1.064 ns | 2.77x |
| Manual Unwrap | ~384 ps | 1.0x (baseline) |
Overhead: ~680 ps (2.77x slower than manual unwrapping)
| Operation | Time |
|---|---|
| Create 7-level chained keypath | ~317 ps |
| Method | Time | Overhead |
|---|---|---|
| Pre-created keypath | ~820 ps | Baseline |
| Created on-the-fly | ~833 ps | +1.6% |
3-Level Deep: ~2.24x overhead (~473 ps)
7-Level Deep: ~2.77x overhead (~680 ps)
Keypath Creation: ~317 ps
The library includes comprehensive tests to verify:
Run tests with:
cargo test --lib
Run benchmarks with:
cargo bench --bench deeply_nested
See the examples/ directory for more comprehensive examples:
deeply_nested.rs - Deeply nested structures with enum variantscontainers.rs - Readable access to all container typeswritable_containers.rs - Writable access to containers with mutation exampleslocks_and_tagged.rs - Synchronization primitives and tagged types (requires tagged and parking_lot features)All container unwrapping methods (for_box(), for_arc(), for_rc()) automatically infer the target type from the Deref trait, eliminating the need for explicit type parameters.
This project is part of the rust-key-paths workspace.
Contributions are welcome! Please ensure all tests pass and benchmarks are updated.
Note: All performance measurements are on a modern CPU. Actual results may vary based on hardware and compiler optimizations.