🔑 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
[]
= "0.8"
= "0.1"
🚀 Examples
See examples/ for many runnable samples. Below are a few highlights.
Widely used - Deeply nested struct
use key_paths_core::KeyPaths;
use key_paths_derive::{Casepaths, Keypaths};
#[derive(Debug, Keypaths)]
struct SomeComplexStruct {
scsf: Option<SomeOtherStruct>,
// scsf2: Option<SomeOtherStruct>,
}
impl SomeComplexStruct {
fn new() -> Self {
Self {
scsf: Some(SomeOtherStruct {
sosf: OneMoreStruct {
omsf: String::from("no value for now"),
omse: SomeEnum::B(DarkStruct { dsf: String::from("dark field") }),
},
}),
}
}
}
#[derive(Debug, Keypaths)]
struct SomeOtherStruct {
sosf: OneMoreStruct,
}
#[derive(Debug, Casepaths)]
enum SomeEnum {
A(String),
B(DarkStruct)
}
#[derive(Debug, Keypaths)]
struct OneMoreStruct {
omsf: String,
omse: SomeEnum
}
#[derive(Debug, Keypaths)]
struct DarkStruct {
dsf: String
}
fn main() {
let op = SomeComplexStruct::scsf_fw()
.then(SomeOtherStruct::sosf_fw())
.then(OneMoreStruct::omse_fw())
.then(SomeEnum::b_case_w())
.then(DarkStruct::dsf_fw());
let mut instance = SomeComplexStruct::new();
let omsf = op.get_mut(&mut instance);
*omsf.unwrap() =
String::from("we can change the field with the other way unclocked by keypaths");
println!("instance = {:?}", instance);
}
1) Structs with #[derive(Keypaths)]
use KeyPaths;
use Keypaths;
2) Optional chaining (failable keypaths)
use KeyPaths;
use Keypaths;
3) Enum CasePaths (readable/writable prisms)
use KeyPaths;
4) Compose enum prisms with struct fields
use KeyPaths;
5) Iteration via keypaths
use KeyPaths;
;
/*
ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(10, 20, 30)) }
ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0, 0, 0)) }
*/
🔗 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).
🛠Roadmap
- Compose across structs, options and enum cases
- Derive macros for automatic keypath generation
- Optional chaining with failable keypaths
- Derive macros for complex multi-field enum variants
📜 License
- Mozilla Public License 2.0