From 1b8d3597116a1c81c73891998acfa222682d2bd6 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:06:22 +0530 Subject: [PATCH 1/8] md updated --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb9a72d..ba204eb 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ fn main() { println!("{garage:?}"); } ``` -### 4. Mutablity +### 4. Mutability ```rust use key_paths_core::KeyPaths; @@ -253,6 +253,20 @@ ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0 --- +## Support + +* Struct Field Support +* Enum Variant Support +* Read / Write +* Mutability support +* Full support of Composition with keypaths including enum +* Helper macros support (WIP) +* Proc macros support (WIP) +* Feature rich, error free and light weigh 3KB only + +--- + + ## 💡 Why use KeyPaths? * Avoids repetitive `match` / `.` chains. From c4812567cf0a19cfb185725bce33bfec98d1a898 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 16:32:44 +0530 Subject: [PATCH 2/8] more eg --- README.md | 2 +- examples/prism_compose2.rs | 110 +++++++++++++++++++++++++++++++++++++ key-paths-core/src/lib.rs | 72 +----------------------- 3 files changed, 112 insertions(+), 72 deletions(-) create mode 100644 examples/prism_compose2.rs diff --git a/README.md b/README.md index ba204eb..52083c4 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0 * [ ] `compose` support for combining multiple key paths. * [ ] Derive macros for automatic KeyPath generation (Upcoming). * [ ] Nested struct & enum traversal. -* [ ] Optional chaining (`User?.profile?.name`) with failable. +* [ ] Optional chaining with failable. --- ## 📜 License diff --git a/examples/prism_compose2.rs b/examples/prism_compose2.rs new file mode 100644 index 0000000..3bd0b5f --- /dev/null +++ b/examples/prism_compose2.rs @@ -0,0 +1,110 @@ +use std::rc::Rc; +use key_paths_core::KeyPaths; +use crate::Product::Apparel; + +#[derive(Debug)] +pub enum Product { + Book(Book), + Electronics(Electronics), + Apparel, +} + +#[derive(Debug)] +pub struct Book { + title: String, + price: f64, +} + +#[derive(Debug)] +pub struct Electronics { + name: String, + price: f64, + warranty: u32, +} + +#[derive(Debug)] +pub struct Inventory { + items: Vec, + shipping_cost: f64, +} + +// Add a helper method to the Product enum for easy display of price +impl Product { + fn price(&self) -> Option<&f64> { + match self { + Product::Book(b) => Some(&b.price), + Product::Electronics(e) => Some(&e.price), + _ => None, + } + } +} + +fn main() { + // invalid syntx as there is nothing in Apparel to read or write. + // let kp:KeyPaths = KeyPaths::writable_enum( + // |v| Product::Apparel, + // |p: &Product| match p { + // Product::Apparel => Some(&()), + // _ => None, + // }, + // |p: &mut Product| match p { + // Product::Apparel => Some(&mut ()), + // _ => None, + // }, + // ); + + + let mut inventory = Inventory { + items: vec![ + Product::Book(Book { + title: "The Rust Programming Language".to_string(), + price: 50.0, + }), + Product::Electronics(Electronics { + name: "Smartphone".to_string(), + price: 699.99, + warranty: 1, + }), + Product::Apparel, + ], + shipping_cost: 5.0, + }; + + + let electronics_path: KeyPaths = KeyPaths::writable_enum( + |v| Product::Electronics(v), + |p: &Product| match p { + Product::Electronics ( electronics) => Some(electronics), + _ => None, + }, + |p: &mut Product| match p { + Product::Electronics (electronics) => Some(electronics), + _ => None, + }, + ); + + let price_path = KeyPaths::failable_writable( + |e: &mut Electronics| Some(&mut e.price) + ); + + // Product -> Electronics -> price + let product_to_price = electronics_path.compose(price_path); + + // Apply the composed KeyPath + if let Some(price) = product_to_price.get_mut(&mut inventory.items[1]) { + println!("Original smartphone price: ${}", price); + *price = 649.99; + println!("New smartphone price: ${:?}", inventory.items[1].price()); + } else { + println!("Path not found for this product."); + } + + // Product -> Book -> price + // Now, try on a product that doesn't match the path + if let Some(_) = product_to_price.get_mut(&mut inventory.items[0]) { + // This won't be executed + } else { + println!("Path not found for the book."); + } + +} \ No newline at end of file diff --git a/key-paths-core/src/lib.rs b/key-paths-core/src/lib.rs index 0a2869b..0eead27 100644 --- a/key-paths-core/src/lib.rs +++ b/key-paths-core/src/lib.rs @@ -156,6 +156,7 @@ where use KeyPaths::*; match (self, mid) { + (Readable(f1), Readable(f2)) => Readable(Rc::new(move |r| f2(f1(r)))), (Writable(f1), Writable(f2)) => Writable(Rc::new(move |r| f2(f1(r)))), @@ -200,77 +201,6 @@ where FailableWritable(Rc::new(move |r| extract_mut(r).map(|m| f2(m)))) } - // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => { - // // FailableWritable(Rc::new(move |r| - // // { - // // // let mut x = extract_mut(r); - // // // x.as_mut().map(|m| f2(m)) - // // // extract_mut(r).map(|m| f2(m)) - // // // extract_mut(r).and_then(|m| f2(m)) - // // // let x = f2(m); - // // extract_mut(r).and_then(|a| f2(a)) - // // - // // } - // // - // // )) - // // FailableWritable(Rc::new(move |r| extract_mut(r).and_then(|a| f2(a)))) - // // FailableWritable(Rc::new(move |r: &mut Root| { - // // match extract_mut(r) { - // // Some(mid) => f2(mid), // mid: &mut Mid -> Option<&mut Value> - // // None => None, - // // } - // // }) as Rc Fn(&'a mut Root) -> Option<&'a mut Value>>) - // - // FailableWritable(Rc::new(move |r: &mut Root| { - // // First extract the intermediate value using extract_mut - // extract_mut(r).and_then(|intermediate| { - // // Now apply f2 to the intermediate value - // // f2 expects &mut Value but intermediate is &mut Value - // f2(intermediate) - // }) - // })) - // } - - // (WritableEnum { extract_mut, .. }, FailableWritable(f2)) => { - // FailableWritable(Rc::new(move |r: &mut Root| { - // // Extract the intermediate Mid value - // let mid_ref = extract_mut(r)?; - // // Apply the second function to get the final Value - // f2(mid_ref) - // })) - // } - - // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => { - // FailableWritable(Rc::new(move |r: &mut Root| { - // // Extract the intermediate Mid value - // let mid_ref = extract_mut(r)?; - // // Apply the second function to get the final Value - // f2(mid_ref) - // })) - // } - - /* (FailableWritable(f2), WritableEnum { extract_mut, .. }) => { - FailableWritable(Rc::new(move |r: &mut Root| { - extract_mut(r).and_then(|intermediate_mid| f2(intermediate_mid)) - })) - } - */ - // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => { - // // Here's the fix: f2 must be a function that operates on a Mid and returns a Value - // // It is already of this type since the 'mid' KeyPaths is KeyPaths - // FailableWritable(Rc::new(move |r: &mut Root| { - // extract_mut(r).and_then(|intermediate_mid| f2(intermediate_mid)) - // })) - // } - - // (FailableWritable(f2), WritableEnum { extract_mut, .. }) => { - // FailableWritable(Rc::new(move |r: &mut Root| -> Option<&mut Value> { - // // Extract the intermediate Mid value - // let mid_ref: &mut Mid = extract_mut(r).unwrap(); - // // Apply the second function to get the final Value - // f2(mid_ref) - // })) - // } ( FailableWritable(f_root_mid), WritableEnum { From 207a4024f71c87f61342cbe32117fa7bb283e810 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:13:14 +0530 Subject: [PATCH 3/8] Update prism_compose2.rs --- examples/prism_compose2.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/prism_compose2.rs b/examples/prism_compose2.rs index 3bd0b5f..91ebd05 100644 --- a/examples/prism_compose2.rs +++ b/examples/prism_compose2.rs @@ -1,6 +1,4 @@ -use std::rc::Rc; use key_paths_core::KeyPaths; -use crate::Product::Apparel; #[derive(Debug)] pub enum Product { From dcc9d1bbdb5d26c18b2f5a8e0da38eb5dd8ad860 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:40:08 +0530 Subject: [PATCH 4/8] trait wip --- examples/eg.rs | 73 +++++++++++++++ examples/prism_compose2.rs | 15 +++ src/lib.rs | 183 ++++++++++++++++++++++++++++++++++++- 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 examples/eg.rs diff --git a/examples/eg.rs b/examples/eg.rs new file mode 100644 index 0000000..e5d46da --- /dev/null +++ b/examples/eg.rs @@ -0,0 +1,73 @@ +use rust_key_paths::{ReadKeyPath, Writable, WriteKeyPath}; +use rust_key_paths::Compose; + +// Example usage (SOUND: User actually owns Address) +#[derive(Debug)] +struct Address { + city: String, + zip: String, +} + +#[derive(Debug)] +struct User { + name: String, + age: u32, + address: Address, +} + + +fn main() { + // field keypaths + let user_name = Writable::new( + |user: &User| Some(&user.name), + |user: &mut User| Some(&mut user.name), + |user: &mut User, name: String| user.name = name, + ); + + let user_address = Writable::new( + |user: &User| Some(&user.address), + |user: &mut User| Some(&mut user.address), + |user: &mut User, addr: Address| user.address = addr, + ); + + let address_city = Writable::new( + |addr: &Address| Some(&addr.city), + |addr: &mut Address| Some(&mut addr.city), + |addr: &mut Address, city: String| addr.city = city, + ); + + // Compose: User -> Address -> city + let user_city = user_address.then::>(address_city); + + let mut user = User { + name: "Alice".to_string(), + age: 30, + address: Address { + city: "Default".to_string(), + zip: "00000".to_string(), + }, + }; + + // Read usage + if let Some(name) = user_name.get(&user) { + println!("User name: {}", name); + } + + // Write usage + if let Some(name_mut) = user_name.get_mut(&mut user) { + *name_mut = "Bob".to_string(); + } + user_name.set(&mut user, "Charlie".to_string()); + println!("User after set: {:?}", user); + + // Composed read + if let Some(city) = user_city.get(&user) { + println!("User city: {}", city); + } + + // Composed write + if let Some(city_mut) = user_city.get_mut(&mut user) { + *city_mut = "Wonderland".to_string(); + } + println!("User after city change: {:?}", user); +} diff --git a/examples/prism_compose2.rs b/examples/prism_compose2.rs index 91ebd05..6fa9439 100644 --- a/examples/prism_compose2.rs +++ b/examples/prism_compose2.rs @@ -1,5 +1,20 @@ use key_paths_core::KeyPaths; +// Example usage (SOUND: User actually owns Address) +#[derive(Debug)] +struct Address { + city: String, + zip: String, +} + +#[derive(Debug)] +struct User { + name: String, + age: u32, + address: Address, +} + + #[derive(Debug)] pub enum Product { Book(Book), diff --git a/src/lib.rs b/src/lib.rs index 4121bb4..9ebe2b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,182 @@ -pub use key_paths_core::*; +use std::rc::Rc; + +// Core traits +pub trait ReadKeyPath { + fn get<'a>(&self, root: &'a Root) -> Option<&'a Value>; +} + +pub trait WriteKeyPath { + fn get_mut<'a>(&self, root: &'a mut Root) -> Option<&'a mut Value>; + fn set(&self, root: &mut Root, value: Value); +} + +pub trait EmbedKeyPath { + fn embed(&self, value: Value) -> Root; +} + +// Concrete implementations +pub struct FailableWritable { + func: Rc Fn(&'a mut Root) -> Option<&'a mut Value>>, +} + +pub struct Writable { + get: Rc Fn(&'a Root) -> Option<&'a Value>>, + get_mut: Rc Fn(&'a mut Root) -> Option<&'a mut Value>>, + set_func: Rc, +} + +pub struct WritableEmbed { + get: Rc Fn(&'a Root) -> Option<&'a Value>>, + get_mut: Rc Fn(&'a mut Root) -> Option<&'a mut Value>>, + set_func: Rc, + embed: Rc Root>, +} + +// Implement ReadKeyPath for FailableWritable +impl ReadKeyPath for FailableWritable { + fn get<'a>(&self, _root: &'a Root) -> Option<&'a Value> { + None // FailableWritable can't provide read access + } +} + +// Implement WriteKeyPath for FailableWritable +impl WriteKeyPath for FailableWritable { + fn get_mut<'a>(&self, root: &'a mut Root) -> Option<&'a mut Value> { + (self.func)(root) + } + + fn set(&self, root: &mut Root, value: Value) { + if let Some(target) = self.get_mut(root) { + *target = value; + } + } +} + +// Implement ReadKeyPath for Writable +impl ReadKeyPath for Writable { + fn get<'a>(&self, root: &'a Root) -> Option<&'a Value> { + (self.get)(root) + } +} + +// Implement WriteKeyPath for Writable +impl WriteKeyPath for Writable { + fn get_mut<'a>(&self, root: &'a mut Root) -> Option<&'a mut Value> { + (self.get_mut)(root) + } + + fn set(&self, root: &mut Root, value: Value) { + (self.set_func)(root, value) + } +} + +// Implement ReadKeyPath for WritableEmbed +impl ReadKeyPath for WritableEmbed { + fn get<'a>(&self, root: &'a Root) -> Option<&'a Value> { + (self.get)(root) + } +} + +// Implement WriteKeyPath for WritableEmbed +impl WriteKeyPath for WritableEmbed { + fn get_mut<'a>(&self, root: &'a mut Root) -> Option<&'a mut Value> { + (self.get_mut)(root) + } + + fn set(&self, root: &mut Root, value: Value) { + (self.set_func)(root, value) + } +} + +// Implement EmbedKeyPath for WritableEmbed +impl EmbedKeyPath for WritableEmbed { + fn embed(&self, value: Value) -> Root { + (self.embed)(value) + } +} + +// Composition wrapper - now with explicit Mid type +pub struct ComposedKeyPath { + first: First, + second: Second, + _phantom: std::marker::PhantomData, +} + +// Implement ReadKeyPath for ComposedKeyPath +impl ReadKeyPath +for ComposedKeyPath +where + First: ReadKeyPath, + Second: ReadKeyPath, + Mid: 'static, +{ + fn get<'a>(&self, root: &'a Root) -> Option<&'a Value> { + let mid = self.first.get(root)?; + self.second.get(mid) + } +} + +// Implement WriteKeyPath for ComposedKeyPath +impl WriteKeyPath +for ComposedKeyPath +where + First: WriteKeyPath, + Second: WriteKeyPath, + Mid: 'static, +{ + fn get_mut<'a>(&self, root: &'a mut Root) -> Option<&'a mut Value> { + let mid = self.first.get_mut(root)?; + self.second.get_mut(mid) + } + + fn set(&self, root: &mut Root, value: Value) { + if let Some(mid) = self.first.get_mut(root) { + self.second.set(mid, value); + } + } +} + +// ---------- Composition trait (constrains Root properly) ---------- +pub trait Compose: Sized + ReadKeyPath { + fn then(self, second: Second) -> ComposedKeyPath + where + Second: ReadKeyPath; +} + +// Blanket impl for anything that can read Root -> Mid +impl Compose for First +where + First: Sized + ReadKeyPath, +{ + fn then(self, second: Second) -> ComposedKeyPath + where + Second: ReadKeyPath, + { + ComposedKeyPath { + first: self, + second, + _phantom: std::marker::PhantomData, + } + } +} + +// Builders +impl FailableWritable { + pub fn new(func: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static) -> Self { + Self { func: Rc::new(func) } + } +} + +impl Writable { + pub fn new( + get: impl for<'a> Fn(&'a Root) -> Option<&'a Value> + 'static, + get_mut: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static, + set: impl Fn(&mut Root, Value) + 'static, + ) -> Self { + Self { + get: Rc::new(get), + get_mut: Rc::new(get_mut), + set_func: Rc::new(set), + } + } +} From e7108bcfbf5e3e18be0c40edacf001b7dad36160 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:51:47 +0530 Subject: [PATCH 5/8] docs + versoning --- examples/eg2.rs | 0 key-paths-core/Cargo.toml | 2 +- key-paths-core/README.md | 289 ++++++++++++++++++ .../examples}/basics.rs | 0 .../examples}/compose.rs | 0 .../examples}/enum_keypath_example.rs | 0 .../examples}/failable.rs | 0 .../examples}/failable_writable.rs | 0 .../examples}/iters.rs | 0 .../examples}/prism.rs | 0 .../examples}/prism_compose.rs | 0 .../examples}/prism_compose2.rs | 0 .../examples}/readable_keypath_example.rs | 0 .../examples}/writable_keypath_example.rs | 0 14 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 examples/eg2.rs rename {examples => key-paths-core/examples}/basics.rs (100%) rename {examples => key-paths-core/examples}/compose.rs (100%) rename {examples => key-paths-core/examples}/enum_keypath_example.rs (100%) rename {examples => key-paths-core/examples}/failable.rs (100%) rename {examples => key-paths-core/examples}/failable_writable.rs (100%) rename {examples => key-paths-core/examples}/iters.rs (100%) rename {examples => key-paths-core/examples}/prism.rs (100%) rename {examples => key-paths-core/examples}/prism_compose.rs (100%) rename {examples => key-paths-core/examples}/prism_compose2.rs (100%) rename {examples => key-paths-core/examples}/readable_keypath_example.rs (100%) rename {examples => key-paths-core/examples}/writable_keypath_example.rs (100%) diff --git a/examples/eg2.rs b/examples/eg2.rs new file mode 100644 index 0000000..e69de29 diff --git a/key-paths-core/Cargo.toml b/key-paths-core/Cargo.toml index 72eebfc..e14ffb8 100644 --- a/key-paths-core/Cargo.toml +++ b/key-paths-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "key-paths-core" -version = "0.6.0" +version = "0.7.0" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" diff --git a/key-paths-core/README.md b/key-paths-core/README.md index e69de29..52083c4 100644 --- a/key-paths-core/README.md +++ b/key-paths-core/README.md @@ -0,0 +1,289 @@ +# 🔑 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 crate lets you work with **struct fields** and **enum variants** as *first-class values*. + +--- + +## ✨ Features + +* ✅ **ReadableKeyPath** → safely read struct fields. +* ✅ **WritableKeyPath** → safely read/write struct fields. +* ✅ **EnumKeyPath (CasePaths)** → extract and embed enum variants. +* ✅ **Composable** → chain key paths together(Upcoming). +* ✅ **Iterable** → iterate or mutate values across collections. +* ✅ **Macros** → concise `readable_keypath!`, `writable_keypath!`, `enum_keypath!`. + +--- + +## 📦 Installation + +```toml +[dependencies] +key_paths_core = "0.6" +``` + +--- + +## 🚀 Examples - Go to latest examples directory docs will be updated later + +### 1. CasePaths with Enums + +```rust +use key_paths_core::KeyPaths; +#[derive(Debug)] +enum Payment { + Cash { amount: u32 }, + Card { number: String, cvv: String }, +} + +fn main() { + let kp = KeyPaths::writable_enum( + |v| Payment::Cash { amount: v }, + |p: &Payment| match p { + Payment::Cash { amount } => Some(amount), + _ => None, + }, + |p: &mut Payment| match p { + Payment::Cash { amount } => Some(amount), + _ => None, + }, + + ); + + let mut p = Payment::Cash { amount: 10 }; + + println!("{:?}", p); + + if let Some(v) = kp.get_mut(&mut p) { + *v = 34 + } + println!("{:?}", p); +} +``` + +--- + +### 2. Readable KeyPaths - helper macros wip + +```rust +use key_paths_core::KeyPaths; + +#[derive(Debug)] +struct Size { + width: u32, + height: u32, +} + +#[derive(Debug)] +struct Rectangle { + size: Size, + name: String, +} + +fn main() { + let mut rect = Rectangle { + size: Size { + width: 30, + height: 50, + }, + name: "MyRect".into(), + }; + + let width_direct = KeyPaths::readable(|r: &Rectangle| &r.size.width); + println!("Width: {:?}", width_direct.get(&rect)); +} +``` + +--- + +### 3. Writable KeyPaths - helper macros wip + +```rust +use key_paths_core::KeyPaths; + +#[derive(Debug)] +struct Size { + width: u32, + height: u32, +} +#[derive(Debug)] +struct Rectangle { + size: Size, + name: String, +} +fn main() { + let mut rect = Rectangle { + size: Size { + width: 30, + height: 50, + }, + name: "MyRect".into(), + }; + let width_mut = KeyPaths::writable( + |r: &mut Rectangle| &mut r.size.width, + ); + // Mutable + if let Some(hp_mut) = width_mut.get_mut(&mut rect) { + *hp_mut += 50; + } + println!("Updated rectangle: {:?}", rect); +} +``` + +### 4. Composability and failablity + ```rust +use key_paths_core::KeyPaths; + +#[derive(Debug)] +struct Engine { + horsepower: u32, +} +#[derive(Debug)] +struct Car { + engine: Option, +} +#[derive(Debug)] +struct Garage { + car: Option, +} + +fn main() { + let mut garage = Garage { + car: Some(Car { + engine: Some(Engine { horsepower: 120 }), + }), + }; + + let kp_car = KeyPaths::failable_writable(|g: &mut Garage| g.car.as_mut()); + let kp_engine = KeyPaths::failable_writable(|c: &mut Car| c.engine.as_mut()); + let kp_hp = KeyPaths::failable_writable(|e: &mut Engine| Some(&mut e.horsepower)); + + // Compose: Garage -> Car -> Engine -> horsepower + let kp = kp_car.compose(kp_engine).compose(kp_hp); + + println!("{garage:?}"); + if let Some(hp) = kp.get_mut(&mut garage) { + *hp = 200; + } + + println!("{garage:?}"); +} +``` +### 4. Mutability + ```rust +use key_paths_core::KeyPaths; + +#[derive(Debug)] +struct Size { + width: u32, + height: u32, +} +#[derive(Debug)] +enum Color { + Red, + Green, + Blue, + Other(RGBU8), +} +#[derive(Debug)] +struct RGBU8(u8, u8, u8); + +#[derive(Debug)] +struct ABox { + name: String, + size: Size, + color: Color, +} +#[derive(Debug)] +struct Rectangle { + size: Size, + name: String, +} +fn main() { + let mut a_box = ABox { + name: String::from("A box"), + size: Size { + width: 10, + height: 20, + }, + color: Color::Other( + RGBU8(10, 20, 30) + ), + }; + + let color_kp: KeyPaths = KeyPaths::failable_writable(|x: &mut ABox| Some(&mut x.color)); + let case_path = KeyPaths::writable_enum( + { + |v| Color::Other(v) + }, + |p: &Color| match p { + Color::Other(rgb) => Some(rgb), + _ => None, + }, + |p: &mut Color| match p { + Color::Other(rgb) => Some(rgb), + _ => None, + }, + + ); + + println!("{:?}", a_box); + let color_rgb_kp = color_kp.compose(case_path); + if let Some(value) = color_rgb_kp.get_mut(&mut a_box) { + *value = RGBU8(0, 0, 0); + } + println!("{:?}", a_box); +} +/* +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](https://lodash.com/docs/4.17.15#get) +* 📘 [Swift KeyPath documentation](https://developer.apple.com/documentation/swift/keypath) +* 📘 [Elm Architecture & Functional Lenses](https://guide.elm-lang.org/architecture/) +* 📘 [Rust Macros Book](https://doc.rust-lang.org/book/ch19-06-macros.html) +* 📘 [Category Theory in FP (for intuition)](https://bartoszmilewski.com/2014/11/24/category-the-essence-of-composition/) + +--- + +## Support + +* Struct Field Support +* Enum Variant Support +* Read / Write +* Mutability support +* Full support of Composition with keypaths including enum +* Helper macros support (WIP) +* Proc macros support (WIP) +* Feature rich, error free and light weigh 3KB only + +--- + + +## 💡 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` support for combining multiple key paths. +* [ ] Derive macros for automatic KeyPath generation (Upcoming). +* [ ] Nested struct & enum traversal. +* [ ] Optional chaining with failable. +--- + +## 📜 License + +* Mozilla Public License 2.0 \ No newline at end of file diff --git a/examples/basics.rs b/key-paths-core/examples/basics.rs similarity index 100% rename from examples/basics.rs rename to key-paths-core/examples/basics.rs diff --git a/examples/compose.rs b/key-paths-core/examples/compose.rs similarity index 100% rename from examples/compose.rs rename to key-paths-core/examples/compose.rs diff --git a/examples/enum_keypath_example.rs b/key-paths-core/examples/enum_keypath_example.rs similarity index 100% rename from examples/enum_keypath_example.rs rename to key-paths-core/examples/enum_keypath_example.rs diff --git a/examples/failable.rs b/key-paths-core/examples/failable.rs similarity index 100% rename from examples/failable.rs rename to key-paths-core/examples/failable.rs diff --git a/examples/failable_writable.rs b/key-paths-core/examples/failable_writable.rs similarity index 100% rename from examples/failable_writable.rs rename to key-paths-core/examples/failable_writable.rs diff --git a/examples/iters.rs b/key-paths-core/examples/iters.rs similarity index 100% rename from examples/iters.rs rename to key-paths-core/examples/iters.rs diff --git a/examples/prism.rs b/key-paths-core/examples/prism.rs similarity index 100% rename from examples/prism.rs rename to key-paths-core/examples/prism.rs diff --git a/examples/prism_compose.rs b/key-paths-core/examples/prism_compose.rs similarity index 100% rename from examples/prism_compose.rs rename to key-paths-core/examples/prism_compose.rs diff --git a/examples/prism_compose2.rs b/key-paths-core/examples/prism_compose2.rs similarity index 100% rename from examples/prism_compose2.rs rename to key-paths-core/examples/prism_compose2.rs diff --git a/examples/readable_keypath_example.rs b/key-paths-core/examples/readable_keypath_example.rs similarity index 100% rename from examples/readable_keypath_example.rs rename to key-paths-core/examples/readable_keypath_example.rs diff --git a/examples/writable_keypath_example.rs b/key-paths-core/examples/writable_keypath_example.rs similarity index 100% rename from examples/writable_keypath_example.rs rename to key-paths-core/examples/writable_keypath_example.rs From ff41c15612cfe5c86d4a1f87cd11cf8ede6374f6 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:32:40 +0530 Subject: [PATCH 6/8] wip --- Cargo.toml | 6 +- README.md | 2 +- examples/casepath.rs | 0 examples/composeability.rs | 0 examples/eg.rs | 212 ++++++++++++++++++++++++++++------ examples/eg2.rs | 1 + examples/failiblity.rs | 0 examples/mutablity.rs | 0 examples/readable_keypaths.rs | 0 examples/writable_keypaths.rs | 0 key-paths-core/README.md | 2 +- key-paths-core/src/lib.rs | 3 +- src/lib.rs | 9 +- 13 files changed, 189 insertions(+), 46 deletions(-) create mode 100644 examples/casepath.rs create mode 100644 examples/composeability.rs create mode 100644 examples/failiblity.rs create mode 100644 examples/mutablity.rs create mode 100644 examples/readable_keypaths.rs create mode 100644 examples/writable_keypaths.rs diff --git a/Cargo.toml b/Cargo.toml index fd5e8b1..fc131f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-key-paths" -version = "0.6.0" +version = "0.7.0" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" @@ -13,7 +13,7 @@ readme = "./README.md" include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"] [dependencies] -key-paths-core = { path = "key-paths-core", version = "0.6.0" } +#key-paths-core = { path = "key-paths-core", version = "0.6.0" } [workspace] @@ -23,7 +23,7 @@ members = [ ] [patch.crates-io] -key-paths-core = { path = "key-paths-core" } +#key-paths-core = { path = "key-paths-core" } [features] diff --git a/README.md b/README.md index 52083c4..1b5c351 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Inspired by **Swift’s KeyPath / CasePath** system, this crate lets you work wi ```toml [dependencies] -key_paths_core = "0.6" +rust_key_paths_ = "0.7" ``` --- diff --git a/examples/casepath.rs b/examples/casepath.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/composeability.rs b/examples/composeability.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/eg.rs b/examples/eg.rs index e5d46da..340c15d 100644 --- a/examples/eg.rs +++ b/examples/eg.rs @@ -1,73 +1,213 @@ -use rust_key_paths::{ReadKeyPath, Writable, WriteKeyPath}; +use rust_key_paths::{FailableWritable, ReadKeyPath, Writable, WriteKeyPath}; use rust_key_paths::Compose; -// Example usage (SOUND: User actually owns Address) -#[derive(Debug)] -struct Address { - city: String, - zip: String, -} +// ========== EXAMPLES ========== -#[derive(Debug)] +// Example 1: Nested structs +#[derive(Debug, Clone)] struct User { name: String, age: u32, address: Address, } +#[derive(Debug, Clone)] +struct Address { + street: String, + city: String, + zip_code: String, +} -fn main() { - // field keypaths - let user_name = Writable::new( +// Create keypaths for nested struct access +fn user_name_keypath() -> Writable { + Writable::new( |user: &User| Some(&user.name), |user: &mut User| Some(&mut user.name), |user: &mut User, name: String| user.name = name, - ); + ) +} - let user_address = Writable::new( +fn user_address_keypath() -> Writable { + Writable::new( |user: &User| Some(&user.address), |user: &mut User| Some(&mut user.address), - |user: &mut User, addr: Address| user.address = addr, - ); + |user: &mut User, address: Address| user.address = address, + ) +} - let address_city = Writable::new( +fn address_city_keypath() -> Writable { + Writable::new( |addr: &Address| Some(&addr.city), |addr: &mut Address| Some(&mut addr.city), |addr: &mut Address, city: String| addr.city = city, - ); + ) +} - // Compose: User -> Address -> city - let user_city = user_address.then::>(address_city); +// Example 2: Enum with variants +#[derive(Debug, Clone)] +enum Contact { + Email(String), + Phone(String), + Address(Address), + Unknown, +} + +#[derive(Debug, Clone)] +struct Profile { + name: String, + contact: Contact, +} + +// Keypath for enum variant access (failable since variant might not match) +fn contact_email_keypath() -> FailableWritable { + FailableWritable::new(|contact: &mut Contact| { + match contact { + Contact::Email(email) => Some(email), + _ => None, + } + }) +} + +fn profile_contact_keypath() -> Writable { + Writable::new( + |profile: &Profile| Some(&profile.contact), + |profile: &mut Profile| Some(&mut profile.contact), + |profile: &mut Profile, contact: Contact| profile.contact = contact, + ) +} + +// Example 3: Complex nested structure +#[derive(Debug, Clone)] +struct Company { + name: String, + employees: Vec, +} + +#[derive(Debug, Clone)] +struct Employee { + id: u32, + profile: Profile, + salary: f64, +} + +fn main() { + println!("=== Nested Struct Example ==="); let mut user = User { name: "Alice".to_string(), age: 30, address: Address { - city: "Default".to_string(), - zip: "00000".to_string(), + street: "123 Main St".to_string(), + city: "Springfield".to_string(), + zip_code: "12345".to_string(), }, }; - // Read usage - if let Some(name) = user_name.get(&user) { + // Basic keypath usage + let name_kp = user_name_keypath(); + if let Some(name) = name_kp.get(&user) { println!("User name: {}", name); } - // Write usage - if let Some(name_mut) = user_name.get_mut(&mut user) { - *name_mut = "Bob".to_string(); - } - user_name.set(&mut user, "Charlie".to_string()); - println!("User after set: {:?}", user); + name_kp.set(&mut user, "Bob".to_string()); + println!("User after name change: {:?}", user); - // Composed read - if let Some(city) = user_city.get(&user) { + // Composition: User -> Address -> City + let user_city_kp = user_address_keypath().then(address_city_keypath()); + + if let Some(city) = user_city_kp.get(&user) { println!("User city: {}", city); } - // Composed write - if let Some(city_mut) = user_city.get_mut(&mut user) { - *city_mut = "Wonderland".to_string(); - } + user_city_kp.set(&mut user, "Metropolis".to_string()); println!("User after city change: {:?}", user); -} + + println!("\n=== Enum Example ==="); + + let mut profile = Profile { + name: "Charlie".to_string(), + contact: Contact::Email("charlie@example.com".to_string()), + }; + + let contact_kp = profile_contact_keypath(); + let email_kp = contact_email_keypath(); + + // Compose profile -> contact -> email (failable) + let profile_email_kp = contact_kp.then(email_kp); + + if let Some(email) = profile_email_kp.get(&profile) { + println!("Profile email: {}", email); + } + + // This will work because contact is Email variant + profile_email_kp.set(&mut profile, "charlie.new@example.com".to_string()); + println!("Profile after email change: {:?}", profile); + + // // Change contact to Phone variant (email access will now fail) + // contact_kp.set(&mut profile, Contact::Phone("555-1234".to_string())); + + if let Some(email) = profile_email_kp.get(&profile) { + println!("Profile email: {}", email); + } else { + println!("No email found (contact is now Phone variant)"); + } + + println!("\n=== Complex Nested Example ==="); + + let mut company = Company { + name: "Tech Corp".to_string(), + employees: vec![ + Employee { + id: 1, + profile: Profile { + name: "Dave".to_string(), + contact: Contact::Email("dave@tech.com".to_string()), + }, + salary: 50000.0, + }, + Employee { + id: 2, + profile: Profile { + name: "Eve".to_string(), + contact: Contact::Phone("555-6789".to_string()), + }, + salary: 60000.0, + }, + ], + }; + + // Create keypath for first employee's email + let first_employee_kp = Writable::new( + |company: &Company| company.employees.first(), + |company: &mut Company| company.employees.first_mut(), + |company: &mut Company, employee: Employee| { + if !company.employees.is_empty() { + company.employees[0] = employee; + } + }, + ); + + let employee_profile_kp = Writable::new( + |employee: &Employee| Some(&employee.profile), + |employee: &mut Employee| Some(&mut employee.profile), + |employee: &mut Employee, profile: Profile| employee.profile = profile, + ); + + // Compose: Company -> first Employee -> Profile -> Contact -> Email + let company_first_employee_email_kp = first_employee_kp + .then(employee_profile_kp) + .then(profile_contact_keypath()) + .then(contact_email_keypath()); + + if let Some(email) = company_first_employee_email_kp.get(&company) { + println!("First employee email: {}", email); + } + + // This will work for the first employee (who has email) + company_first_employee_email_kp.set( + &mut company, + "dave.new@tech.com".to_string() + ); + + println!("Company after email change: {:?}", company); +} \ No newline at end of file diff --git a/examples/eg2.rs b/examples/eg2.rs index e69de29..e71fdf5 100644 --- a/examples/eg2.rs +++ b/examples/eg2.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/failiblity.rs b/examples/failiblity.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/mutablity.rs b/examples/mutablity.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/readable_keypaths.rs b/examples/readable_keypaths.rs new file mode 100644 index 0000000..e69de29 diff --git a/examples/writable_keypaths.rs b/examples/writable_keypaths.rs new file mode 100644 index 0000000..e69de29 diff --git a/key-paths-core/README.md b/key-paths-core/README.md index 52083c4..2ac5311 100644 --- a/key-paths-core/README.md +++ b/key-paths-core/README.md @@ -20,7 +20,7 @@ Inspired by **Swift’s KeyPath / CasePath** system, this crate lets you work wi ```toml [dependencies] -key_paths_core = "0.6" +key_paths_core = "0.7" ``` --- diff --git a/key-paths-core/src/lib.rs b/key-paths-core/src/lib.rs index 0eead27..c43ff72 100644 --- a/key-paths-core/src/lib.rs +++ b/key-paths-core/src/lib.rs @@ -1,6 +1,7 @@ use std::rc::Rc; -// #[derive(Clone)] +/// Go to examples section to see the implementations +/// pub enum KeyPaths { Readable(Rc Fn(&'a Root) -> &'a Value>), ReadableEnum { diff --git a/src/lib.rs b/src/lib.rs index 9ebe2b5..0739461 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ impl EmbedKeyPath for WritableEmbed { } } -// Composition wrapper - now with explicit Mid type +// Composition wrapper pub struct ComposedKeyPath { first: First, second: Second, @@ -136,14 +136,14 @@ where } } -// ---------- Composition trait (constrains Root properly) ---------- +// Composition trait pub trait Compose: Sized + ReadKeyPath { fn then(self, second: Second) -> ComposedKeyPath where Second: ReadKeyPath; } -// Blanket impl for anything that can read Root -> Mid +// Blanket implementation impl Compose for First where First: Sized + ReadKeyPath, @@ -160,7 +160,7 @@ where } } -// Builders +// Builder functions impl FailableWritable { pub fn new(func: impl for<'a> Fn(&'a mut Root) -> Option<&'a mut Value> + 'static) -> Self { Self { func: Rc::new(func) } @@ -180,3 +180,4 @@ impl Writable { } } } + From 7df334773079b0f532b133a528bcf24353b6188d Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:35:26 +0530 Subject: [PATCH 7/8] eg wip --- examples/casepath.rs | 1 + examples/composeability.rs | 1 + examples/failiblity.rs | 1 + examples/mutablity.rs | 1 + examples/readable_keypaths.rs | 1 + examples/writable_keypaths.rs | 1 + 6 files changed, 6 insertions(+) diff --git a/examples/casepath.rs b/examples/casepath.rs index e69de29..e71fdf5 100644 --- a/examples/casepath.rs +++ b/examples/casepath.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/composeability.rs b/examples/composeability.rs index e69de29..e71fdf5 100644 --- a/examples/composeability.rs +++ b/examples/composeability.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/failiblity.rs b/examples/failiblity.rs index e69de29..e71fdf5 100644 --- a/examples/failiblity.rs +++ b/examples/failiblity.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/mutablity.rs b/examples/mutablity.rs index e69de29..e71fdf5 100644 --- a/examples/mutablity.rs +++ b/examples/mutablity.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/readable_keypaths.rs b/examples/readable_keypaths.rs index e69de29..e71fdf5 100644 --- a/examples/readable_keypaths.rs +++ b/examples/readable_keypaths.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file diff --git a/examples/writable_keypaths.rs b/examples/writable_keypaths.rs index e69de29..e71fdf5 100644 --- a/examples/writable_keypaths.rs +++ b/examples/writable_keypaths.rs @@ -0,0 +1 @@ +fn main() {} \ No newline at end of file From 7007904d7693df6e62ccbea11691e64efbaca0b6 Mon Sep 17 00:00:00 2001 From: Akash soni <33283321+akashsoni01@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:43:56 +0530 Subject: [PATCH 8/8] wip --- README.md | 221 +++++++++++++++++++++++++++++++--- examples/eg2.rs | 1 - examples/readable_keypaths.rs | 25 +++- 3 files changed, 226 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1b5c351..1b97d87 100644 --- a/README.md +++ b/README.md @@ -30,35 +30,218 @@ rust_key_paths_ = "0.7" ### 1. CasePaths with Enums ```rust -use key_paths_core::KeyPaths; -#[derive(Debug)] -enum Payment { - Cash { amount: u32 }, - Card { number: String, cvv: String }, +use rust_key_paths::{FailableWritable, ReadKeyPath, Writable, WriteKeyPath}; +use rust_key_paths::Compose; + +// ========== EXAMPLES ========== + +// Example 1: Nested structs +#[derive(Debug, Clone)] +struct User { + name: String, + age: u32, + address: Address, } -fn main() { - let kp = KeyPaths::writable_enum( - |v| Payment::Cash { amount: v }, - |p: &Payment| match p { - Payment::Cash { amount } => Some(amount), - _ => None, - }, - |p: &mut Payment| match p { - Payment::Cash { amount } => Some(amount), +#[derive(Debug, Clone)] +struct Address { + street: String, + city: String, + zip_code: String, +} + +// Create keypaths for nested struct access +fn user_name_keypath() -> Writable { + Writable::new( + |user: &User| Some(&user.name), + |user: &mut User| Some(&mut user.name), + |user: &mut User, name: String| user.name = name, + ) +} + +fn user_address_keypath() -> Writable { + Writable::new( + |user: &User| Some(&user.address), + |user: &mut User| Some(&mut user.address), + |user: &mut User, address: Address| user.address = address, + ) +} + +fn address_city_keypath() -> Writable { + Writable::new( + |addr: &Address| Some(&addr.city), + |addr: &mut Address| Some(&mut addr.city), + |addr: &mut Address, city: String| addr.city = city, + ) +} + +// Example 2: Enum with variants +#[derive(Debug, Clone)] +enum Contact { + Email(String), + Phone(String), + Address(Address), + Unknown, +} + +#[derive(Debug, Clone)] +struct Profile { + name: String, + contact: Contact, +} + +// Keypath for enum variant access (failable since variant might not match) +fn contact_email_keypath() -> FailableWritable { + FailableWritable::new(|contact: &mut Contact| { + match contact { + Contact::Email(email) => Some(email), _ => None, + } + }) +} + +fn profile_contact_keypath() -> Writable { + Writable::new( + |profile: &Profile| Some(&profile.contact), + |profile: &mut Profile| Some(&mut profile.contact), + |profile: &mut Profile, contact: Contact| profile.contact = contact, + ) +} + +// Example 3: Complex nested structure +#[derive(Debug, Clone)] +struct Company { + name: String, + employees: Vec, +} + +#[derive(Debug, Clone)] +struct Employee { + id: u32, + profile: Profile, + salary: f64, +} + +fn main() { + println!("=== Nested Struct Example ==="); + + let mut user = User { + name: "Alice".to_string(), + age: 30, + address: Address { + street: "123 Main St".to_string(), + city: "Springfield".to_string(), + zip_code: "12345".to_string(), }, + }; + + // Basic keypath usage + let name_kp = user_name_keypath(); + if let Some(name) = name_kp.get(&user) { + println!("User name: {}", name); + } + + name_kp.set(&mut user, "Bob".to_string()); + println!("User after name change: {:?}", user); + + // Composition: User -> Address -> City + let user_city_kp = user_address_keypath().then(address_city_keypath()); + + if let Some(city) = user_city_kp.get(&user) { + println!("User city: {}", city); + } + + user_city_kp.set(&mut user, "Metropolis".to_string()); + println!("User after city change: {:?}", user); + + println!("\n=== Enum Example ==="); + + let mut profile = Profile { + name: "Charlie".to_string(), + contact: Contact::Email("charlie@example.com".to_string()), + }; + + let contact_kp = profile_contact_keypath(); + let email_kp = contact_email_keypath(); + // Compose profile -> contact -> email (failable) + let profile_email_kp = contact_kp.then(email_kp); + + if let Some(email) = profile_email_kp.get(&profile) { + println!("Profile email: {}", email); + } + + // This will work because contact is Email variant + profile_email_kp.set(&mut profile, "charlie.new@example.com".to_string()); + println!("Profile after email change: {:?}", profile); + + // // Change contact to Phone variant (email access will now fail) + // contact_kp.set(&mut profile, Contact::Phone("555-1234".to_string())); + + if let Some(email) = profile_email_kp.get(&profile) { + println!("Profile email: {}", email); + } else { + println!("No email found (contact is now Phone variant)"); + } + + println!("\n=== Complex Nested Example ==="); + + let mut company = Company { + name: "Tech Corp".to_string(), + employees: vec![ + Employee { + id: 1, + profile: Profile { + name: "Dave".to_string(), + contact: Contact::Email("dave@tech.com".to_string()), + }, + salary: 50000.0, + }, + Employee { + id: 2, + profile: Profile { + name: "Eve".to_string(), + contact: Contact::Phone("555-6789".to_string()), + }, + salary: 60000.0, + }, + ], + }; + + // Create keypath for first employee's email + let first_employee_kp = Writable::new( + |company: &Company| company.employees.first(), + |company: &mut Company| company.employees.first_mut(), + |company: &mut Company, employee: Employee| { + if !company.employees.is_empty() { + company.employees[0] = employee; + } + }, ); - let mut p = Payment::Cash { amount: 10 }; + let employee_profile_kp = Writable::new( + |employee: &Employee| Some(&employee.profile), + |employee: &mut Employee| Some(&mut employee.profile), + |employee: &mut Employee, profile: Profile| employee.profile = profile, + ); - println!("{:?}", p); + // Compose: Company -> first Employee -> Profile -> Contact -> Email + let company_first_employee_email_kp = first_employee_kp + .then(employee_profile_kp) + .then(profile_contact_keypath()) + .then(contact_email_keypath()); - if let Some(v) = kp.get_mut(&mut p) { - *v = 34 + if let Some(email) = company_first_employee_email_kp.get(&company) { + println!("First employee email: {}", email); } - println!("{:?}", p); + + // This will work for the first employee (who has email) + company_first_employee_email_kp.set( + &mut company, + "dave.new@tech.com".to_string() + ); + + println!("Company after email change: {:?}", company); } ``` diff --git a/examples/eg2.rs b/examples/eg2.rs index e71fdf5..e69de29 100644 --- a/examples/eg2.rs +++ b/examples/eg2.rs @@ -1 +0,0 @@ -fn main() {} \ No newline at end of file diff --git a/examples/readable_keypaths.rs b/examples/readable_keypaths.rs index e71fdf5..0a6c5fa 100644 --- a/examples/readable_keypaths.rs +++ b/examples/readable_keypaths.rs @@ -1 +1,24 @@ -fn main() {} \ No newline at end of file +#[derive(Debug)] +struct Size { + width: u32, + height: u32, +} + +#[derive(Debug)] +struct Rectangle { + size: Size, + name: String, +} + +fn main() { + let mut rect = Rectangle { + size: Size { + width: 30, + height: 50, + }, + name: "MyRect".into(), + }; + + let width_direct = KeyPaths::readable(|r: &Rectangle| &r.size.width); + println!("Width: {:?}", width_direct.get(&rect)); +}