This guide helps you migrate from the dynamic dispatch key-paths-core (v1.6.0) to the static dispatch rust-keypaths (v1.0.0) implementation.
rust-keypaths is a static dispatch, faster alternative to key-paths-core. It provides:
- ✅ Better Performance: Write operations can be faster than manual unwrapping at deeper nesting levels
- ✅ Zero Runtime Overhead: No dynamic dispatch costs
- ✅ Better Compiler Optimizations: Static dispatch allows more aggressive inlining
- ✅ Type Safety: Full compile-time type checking with zero runtime cost
- Want the best performance
- Don't need
Send + Syncbounds - Are starting a new project
- Want better compiler optimizations
- Don't need dynamic dispatch with trait objects
- Need
Send + Syncbounds for multithreaded scenarios - Require dynamic dispatch with trait objects
- Have existing code using the enum-based
KeyPathsAPI - Need compatibility with older versions
[dependencies]
key-paths-core = "1.7.0"
key-paths-derive = "1.1.0"[dependencies]
rust-keypaths = "1.0.0"
keypaths-proc = "1.0.0"use key_paths_core::KeyPaths;
// Readable keypath
let kp: KeyPaths<Struct, String> = KeyPaths::readable(|s: &Struct| &s.field);
// Failable readable keypath
let opt_kp: KeyPaths<Struct, String> = KeyPaths::failable_readable(|s: &Struct| s.opt_field.as_ref());
// Writable keypath
let writable_kp: KeyPaths<Struct, String> = KeyPaths::writable(|s: &mut Struct| &mut s.field);
// Failable writable keypath
let fw_kp: KeyPaths<Struct, String> = KeyPaths::failable_writable(|s: &mut Struct| s.opt_field.as_mut());use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath};
// Readable keypath
let kp = KeyPath::new(|s: &Struct| &s.field);
// Failable readable keypath
let opt_kp = OptionalKeyPath::new(|s: &Struct| s.opt_field.as_ref());
// Writable keypath
let writable_kp = WritableKeyPath::new(|s: &mut Struct| &mut s.field);
// Failable writable keypath
let fw_kp = WritableOptionalKeyPath::new(|s: &mut Struct| s.opt_field.as_mut());use key_paths_core::KeyPaths;
let kp = KeyPaths::readable(|s: &User| &s.name);
let value = kp.get(&user); // Returns Option<&String>use rust_keypaths::KeyPath;
let kp = KeyPath::new(|s: &User| &s.name);
let value = kp.get(&user); // Returns &String (direct, not Option)use key_paths_core::KeyPaths;
let opt_kp = KeyPaths::failable_readable(|s: &User| s.email.as_ref());
let email = opt_kp.get(&user); // Returns Option<&String>use rust_keypaths::OptionalKeyPath;
let opt_kp = OptionalKeyPath::new(|s: &User| s.email.as_ref());
let email = opt_kp.get(&user); // Returns Option<&String> (same)use key_paths_core::KeyPaths;
let kp1 = KeyPaths::failable_readable(|s: &Root| s.level1.as_ref());
let kp2 = KeyPaths::failable_readable(|l1: &Level1| l1.level2.as_ref());
let chained = kp1.compose(kp2);use rust_keypaths::OptionalKeyPath;
let kp1 = OptionalKeyPath::new(|s: &Root| s.level1.as_ref());
let kp2 = OptionalKeyPath::new(|l1: &Level1| l1.level2.as_ref());
let chained = kp1.then(kp2); // Note: method name changed from compose() to then()use key_paths_core::KeyPaths;
let mut user = User { name: "Akash".to_string() };
let kp = KeyPaths::writable(|s: &mut User| &mut s.name);
if let Some(name_ref) = kp.get_mut(&mut user) {
*name_ref = "Bob".to_string();
}use rust_keypaths::WritableKeyPath;
let mut user = User { name: "Akash".to_string() };
let kp = WritableKeyPath::new(|s: &mut User| &mut s.name);
let name_ref = kp.get_mut(&mut user); // Returns &mut String directly (not Option)
*name_ref = "Bob".to_string();use key_paths_core::KeyPaths;
let kp = KeyPaths::failable_readable(|s: &Container| s.boxed.as_ref());
let unwrapped = kp.for_box::<String>(); // Required explicit type parameteruse rust_keypaths::OptionalKeyPath;
let kp = OptionalKeyPath::new(|s: &Container| s.boxed.as_ref());
let unwrapped = kp.for_box(); // Type automatically inferred!use key_paths_core::KeyPaths;
let enum_kp = KeyPaths::readable_enum(|e: &MyEnum| {
match e {
MyEnum::Variant(v) => Some(v),
_ => None,
}
});use rust_keypaths::EnumKeyPaths;
let enum_kp = EnumKeyPaths::for_variant(|e: &MyEnum| {
if let MyEnum::Variant(v) = e {
Some(v)
} else {
None
}
});use key_paths_derive::Keypaths;
#[derive(Kp)]
struct User {
name: String,
email: Option<String>,
}
// Usage
let name_kp = User::name_r(); // Returns KeyPaths<User, String>
let email_kp = User::email_fr(); // Returns KeyPaths<User, String>use keypaths_proc::Kp;
#[derive(Kp)]
struct User {
name: String,
email: Option<String>,
}
// Usage
let name_kp = User::name_r(); // Returns KeyPath<User, String, ...>
let email_kp = User::email_fr(); // Returns OptionalKeyPath<User, String, ...>- Before: Single
KeyPaths<Root, Value>enum type - After: Separate types:
KeyPath,OptionalKeyPath,WritableKeyPath,WritableOptionalKeyPath
compose()→then()for chainingget()onKeyPathreturns&Value(notOption<&Value>)get_mut()onWritableKeyPathreturns&mut Value(notOption<&mut Value>)
- Before:
KeyPaths::owned()andKeyPaths::failable_owned()supported - After: Owned keypaths not supported (use readable/writable instead)
- Before:
KeyPathsenum implementsSend + Sync - After:
rust-keypathstypes do not implementSend + Sync(by design for better performance)
- Container unwrapping methods (
for_box(),for_arc(),for_rc()) no longer require explicit type parameters - types are automatically inferred
# Remove
key-paths-core = "1.7.0"
key-paths-derive = "1.1.0"
# Add
rust-keypaths = "1.0.0"
keypaths-proc = "1.0.0"// Before
use key_paths_core::KeyPaths;
use key_paths_derive::Keypaths;
// After
use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath};
use keypaths_proc::Kp;// Before
let kp = KeyPaths::readable(|s: &Struct| &s.field);
// After
let kp = KeyPath::new(|s: &Struct| &s.field);// Before
let chained = kp1.compose(kp2);
// After
let chained = kp1.then(kp2);// Before - KeyPath returns Option
let kp = KeyPaths::readable(|s: &Struct| &s.field);
if let Some(value) = kp.get(&instance) {
// ...
}
// After - KeyPath returns direct reference
let kp = KeyPath::new(|s: &Struct| &s.field);
let value = kp.get(&instance); // Direct access, no Option// Before
let unwrapped = kp.for_box::<String>();
// After
let unwrapped = kp.for_box(); // Type inferred automaticallyAfter migration, you can expect:
- Write Operations: Can be 2-7% faster than manual unwrapping at deeper nesting levels
- Read Operations: ~2-3x overhead vs manual unwrapping, but absolute time is still sub-nanosecond
- Better Inlining: Compiler can optimize more aggressively
- Zero Dynamic Dispatch: No runtime overhead from trait objects
See BENCHMARK_REPORT.md for detailed performance analysis.
// Before
let kp = KeyPaths::readable(|s: &User| &s.name);
let name = kp.get(&user).unwrap();
// After
let kp = KeyPath::new(|s: &User| &s.name);
let name = kp.get(&user); // No unwrap needed// Before
let kp = KeyPaths::failable_readable(|s: &User| s.email.as_ref());
if let Some(email) = kp.get(&user) {
// ...
}
// After
let kp = OptionalKeyPath::new(|s: &User| s.email.as_ref());
if let Some(email) = kp.get(&user) {
// ...
}// Before
let kp1 = KeyPaths::failable_readable(|r: &Root| r.level1.as_ref());
let kp2 = KeyPaths::failable_readable(|l1: &Level1| l1.level2.as_ref());
let kp3 = KeyPaths::failable_readable(|l2: &Level2| l2.value.as_ref());
let chained = kp1.compose(kp2).compose(kp3);
// After
let kp1 = OptionalKeyPath::new(|r: &Root| r.level1.as_ref());
let kp2 = OptionalKeyPath::new(|l1: &Level1| l1.level2.as_ref());
let kp3 = OptionalKeyPath::new(|l2: &Level2| l2.value.as_ref());
let chained = kp1.then(kp2).then(kp3);// Before
let mut user = User { name: "Akash".to_string() };
let kp = KeyPaths::writable(|s: &mut User| &mut s.name);
if let Some(name_ref) = kp.get_mut(&mut user) {
*name_ref = "Bob".to_string();
}
// After
let mut user = User { name: "Akash".to_string() };
let kp = WritableKeyPath::new(|s: &mut User| &mut s.name);
let name_ref = kp.get_mut(&mut user); // Direct access
*name_ref = "Bob".to_string();Solution: If you need Send + Sync, stay with key-paths-core v1.6.0. rust-keypaths intentionally doesn't implement these traits for better performance.
Solution: Use then() instead of compose().
Solution: KeyPath::get() returns &Value directly, not Option<&Value>. Use OptionalKeyPath if you need Option.
Solution: Remove the type parameter - for_box() now infers types automatically.
- Check the rust-keypaths README for detailed API documentation
- See examples for comprehensive usage examples
- Review benchmark results for performance analysis
Migrating from key-paths-core to rust-keypaths provides:
- ✅ Better performance (especially for write operations)
- ✅ Zero runtime overhead
- ✅ Better compiler optimizations
- ✅ Automatic type inference
⚠️ NoSend + Syncsupport (by design)⚠️ No owned keypaths (use readable/writable instead)
For most use cases, rust-keypaths is the recommended choice. Only use key-paths-core v1.6.0 if you specifically need Send + Sync bounds or dynamic dispatch.