Skip to content

Commit 4ab6b40

Browse files
committed
wip
1 parent cc45404 commit 4ab6b40

File tree

5 files changed

+540
-6
lines changed

5 files changed

+540
-6
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust-key-paths"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
edition = "2024"
55
authors = ["Codefonsi <[email protected]>"]
66
license = "MPL-2.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# 🔑 KeyPaths & CasePaths in Rust
22

33
Key paths and case paths provide a **safe, composable way to access and modify nested data** in Rust.
4-
Inspired by **Swift’s KeyPath / CasePath** system, this crate lets you work with **struct fields** and **enum variants** as *first-class values*.
4+
Inspired by **Swift’s KeyPath / CasePath** system, this this feature rich crate lets you work with **struct fields** and **enum variants** as *first-class values*.
55

66
---
77

examples/basics_macros.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ fn main() {
2323
};
2424

2525
// Define readable and writable keypaths.
26-
let size_kp = KeyPaths::readable(|r: &Rectangle| &r.size);
27-
let width_kp = KeyPaths::readable(|s: &Size| &s.width);
26+
let size_kp: KeyPaths<Rectangle, Size> = KeyPaths::readable(|r: &Rectangle| &r.size);
27+
let width_kp: KeyPaths<Size, u32> = KeyPaths::readable(|s: &Size| &s.width);
2828

2929
// Compose nested paths (assuming composition is supported).
3030
// e.g., rect[&size_kp.then(&width_kp)] — hypothetical chaining
3131

3232
// Alternatively, define them directly:
33-
let width_direct = KeyPaths::readable(|r: &Rectangle| &r.size.width);
33+
let width_direct: KeyPaths<Rectangle, u32> = KeyPaths::readable(|r: &Rectangle| &r.size.width);
3434
println!("Width: {:?}", width_direct.get(&rect));
3535

3636
// Writable keypath for modifying fields:
37-
let width_mut = KeyPaths::writable(
37+
let width_mut: KeyPaths<Rectangle, u32> = KeyPaths::writable(
3838
// |r: &Rectangle| &r.size.width,
3939
|r: &mut Rectangle| &mut r.size.width,
4040
);

key-paths-core/README.md

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# 🔑 KeyPaths & CasePaths in Rust
2+
3+
Key paths and case paths provide a **safe, composable way to access and modify nested data** in Rust.
4+
Inspired by **Swift’s KeyPath / CasePath** system, this feature rich crate lets you work with **struct fields** and **enum variants** as *first-class values*.
5+
6+
---
7+
8+
## ✨ Features
9+
10+
-**Readable/Writable keypaths** for struct fields
11+
-**Failable keypaths** for `Option<T>` chains (`_fr`/`_fw`)
12+
-**Enum CasePaths** (readable and writable prisms)
13+
-**Composition** across structs, options and enum cases
14+
-**Iteration helpers** over collections via keypaths
15+
-**Proc-macros**: `#[derive(Keypaths)]` for structs/tuple-structs and enums, `#[derive(Casepaths)]` for enums
16+
17+
---
18+
19+
## 📦 Installation
20+
21+
```toml
22+
[dependencies]
23+
key-paths-core = "0.6"
24+
key-paths-derive = "0.1"
25+
```
26+
27+
---
28+
29+
## 🚀 Examples
30+
31+
See `examples/` for many runnable samples. Below are a few highlights.
32+
33+
### 1) Structs with #[derive(Keypaths)]
34+
35+
```rust
36+
use key_paths_core::KeyPaths;
37+
use key_paths_derive::Keypaths;
38+
39+
#[derive(Debug, Keypaths)]
40+
struct Size { width: u32, height: u32 }
41+
42+
#[derive(Debug, Keypaths)]
43+
struct Rectangle { size: Size, name: String }
44+
45+
fn main() {
46+
let mut rect = Rectangle { size: Size { width: 30, height: 50 }, name: "MyRect".into() };
47+
48+
// Readable/writable
49+
println!("width = {:?}", Size::width_r().get(&rect.size));
50+
if let Some(w) = Size::width_w().get_mut(&mut rect.size) { *w += 10; }
51+
52+
// Compose: Rectangle -> Size -> width
53+
let rect_width = Rectangle::size_r().compose(Size::width_r());
54+
println!("rect.width = {:?}", rect_width.get(&rect));
55+
}
56+
```
57+
58+
### 2) Optional chaining (failable keypaths)
59+
60+
```rust
61+
use key_paths_core::KeyPaths;
62+
use key_paths_derive::Keypaths;
63+
64+
#[derive(Debug, Keypaths)]
65+
struct Engine { horsepower: u32 }
66+
#[derive(Debug, Keypaths)]
67+
struct Car { engine: Option<Engine> }
68+
#[derive(Debug, Keypaths)]
69+
struct Garage { car: Option<Car> }
70+
71+
fn main() {
72+
let mut g = Garage { car: Some(Car { engine: Some(Engine { horsepower: 120 }) }) };
73+
74+
// Read horsepower if present
75+
let hp = Garage::car_fr()
76+
.compose(Car::engine_fr())
77+
.compose(Engine::horsepower_r());
78+
println!("hp = {:?}", hp.get(&g));
79+
80+
// Mutate horsepower if present
81+
if let Some(car) = Garage::car_fw().get_mut(&mut g) {
82+
if let Some(engine) = Car::engine_fw().get_mut(car) {
83+
if let Some(hp) = Engine::horsepower_w().get_mut(engine) { *hp += 30; }
84+
}
85+
}
86+
}
87+
```
88+
89+
### 3) Enum CasePaths (readable/writable prisms)
90+
91+
```rust
92+
use key_paths_core::KeyPaths;
93+
#[derive(Debug)]
94+
enum Payment {
95+
Cash { amount: u32 },
96+
Card { number: String, cvv: String },
97+
}
98+
99+
fn main() {
100+
let kp = KeyPaths::writable_enum(
101+
|v| Payment::Cash { amount: v },
102+
|p: &Payment| match p {
103+
Payment::Cash { amount } => Some(amount),
104+
_ => None,
105+
},
106+
|p: &mut Payment| match p {
107+
Payment::Cash { amount } => Some(amount),
108+
_ => None,
109+
},
110+
111+
);
112+
113+
let mut p = Payment::Cash { amount: 10 };
114+
115+
println!("{:?}", p);
116+
117+
if let Some(v) = kp.get_mut(&mut p) {
118+
*v = 34
119+
}
120+
println!("{:?}", p);
121+
}
122+
```
123+
124+
---
125+
126+
### 4) Compose enum prisms with struct fields
127+
```rust
128+
use key_paths_core::KeyPaths;
129+
130+
#[derive(Debug)]
131+
struct Engine {
132+
horsepower: u32,
133+
}
134+
#[derive(Debug)]
135+
struct Car {
136+
engine: Option<Engine>,
137+
}
138+
#[derive(Debug)]
139+
struct Garage {
140+
car: Option<Car>,
141+
}
142+
143+
fn main() {
144+
let mut garage = Garage {
145+
car: Some(Car {
146+
engine: Some(Engine { horsepower: 120 }),
147+
}),
148+
};
149+
150+
let kp_car = KeyPaths::failable_writable(|g: &mut Garage| g.car.as_mut());
151+
let kp_engine = KeyPaths::failable_writable(|c: &mut Car| c.engine.as_mut());
152+
let kp_hp = KeyPaths::failable_writable(|e: &mut Engine| Some(&mut e.horsepower));
153+
154+
// Compose: Garage -> Car -> Engine -> horsepower
155+
let kp = kp_car.compose(kp_engine).compose(kp_hp);
156+
157+
println!("{garage:?}");
158+
if let Some(hp) = kp.get_mut(&mut garage) {
159+
*hp = 200;
160+
}
161+
162+
println!("{garage:?}");
163+
}
164+
```
165+
### 5) Iteration via keypaths
166+
```rust
167+
use key_paths_core::KeyPaths;
168+
169+
#[derive(Debug)]
170+
struct Size {
171+
width: u32,
172+
height: u32,
173+
}
174+
#[derive(Debug)]
175+
enum Color {
176+
Red,
177+
Green,
178+
Blue,
179+
Other(RGBU8),
180+
}
181+
#[derive(Debug)]
182+
struct RGBU8(u8, u8, u8);
183+
184+
#[derive(Debug)]
185+
struct ABox {
186+
name: String,
187+
size: Size,
188+
color: Color,
189+
}
190+
#[derive(Debug)]
191+
struct Rectangle {
192+
size: Size,
193+
name: String,
194+
}
195+
fn main() {
196+
let mut a_box = ABox {
197+
name: String::from("A box"),
198+
size: Size {
199+
width: 10,
200+
height: 20,
201+
},
202+
color: Color::Other(
203+
RGBU8(10, 20, 30)
204+
),
205+
};
206+
207+
let color_kp: KeyPaths<ABox, Color> = KeyPaths::failable_writable(|x: &mut ABox| Some(&mut x.color));
208+
let case_path = KeyPaths::writable_enum(
209+
{
210+
|v| Color::Other(v)
211+
},
212+
|p: &Color| match p {
213+
Color::Other(rgb) => Some(rgb),
214+
_ => None,
215+
},
216+
|p: &mut Color| match p {
217+
Color::Other(rgb) => Some(rgb),
218+
_ => None,
219+
},
220+
221+
);
222+
223+
println!("{:?}", a_box);
224+
let color_rgb_kp = color_kp.compose(case_path);
225+
if let Some(value) = color_rgb_kp.get_mut(&mut a_box) {
226+
*value = RGBU8(0, 0, 0);
227+
}
228+
println!("{:?}", a_box);
229+
}
230+
/*
231+
ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(10, 20, 30)) }
232+
ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0, 0, 0)) }
233+
*/
234+
```
235+
236+
---
237+
238+
## 🔗 Helpful Links & Resources
239+
240+
* 📘 [type-safe property paths](https://lodash.com/docs/4.17.15#get)
241+
* 📘 [Swift KeyPath documentation](https://developer.apple.com/documentation/swift/keypath)
242+
* 📘 [Elm Architecture & Functional Lenses](https://guide.elm-lang.org/architecture/)
243+
* 📘 [Rust Macros Book](https://doc.rust-lang.org/book/ch19-06-macros.html)
244+
* 📘 [Category Theory in FP (for intuition)](https://bartoszmilewski.com/2014/11/24/category-the-essence-of-composition/)
245+
246+
---
247+
248+
## 💡 Why use KeyPaths?
249+
250+
* Avoids repetitive `match` / `.` chains.
251+
* Encourages **compositional design**.
252+
* Plays well with **DDD (Domain-Driven Design)** and **Actor-based systems**.
253+
* Useful for **reflection-like behaviors** in Rust (without unsafe).
254+
255+
---
256+
257+
## 🛠 Roadmap
258+
259+
- [x] Compose across structs, options and enum cases
260+
- [x] Derive macros for automatic keypath generation
261+
- [x] Optional chaining with failable keypaths
262+
- [ ] Derive macros for complex multi-field enum variants
263+
---
264+
265+
## 📜 License
266+
267+
* Mozilla Public License 2.0

0 commit comments

Comments
 (0)