Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ target
key-paths-core/.idea/vcs.xml
key-paths-core/.idea/tagged-core.iml
key-paths-core/.idea/modules.xml
key-paths-core/.idea/key-paths-core.iml
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-key-paths"
version = "0.2.0"
version = "0.4.0"
edition = "2024"
authors = ["Codefonsi <info@codefonsi.com>"]
license = "MPL-2.0"
Expand All @@ -13,7 +13,7 @@ readme = "./README.md"
include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"]

[dependencies]
key-paths-core = { path = "key-paths-core", version = "0.1.0" }
key-paths-core = { path = "key-paths-core", version = "0.4.0" }


[workspace]
Expand Down
160 changes: 143 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Inspired by **Swift’s KeyPath / CasePath** system, this crate lets you work wi

```toml
[dependencies]
key_paths_core = "0.1"
key_paths_core = "0.3"
```

---
Expand Down Expand Up @@ -70,9 +70,7 @@ fn main() {
### 2. Readable KeyPaths

```rust
use key_paths_core::Readable;
use key_paths_core::ReadableKeyPath;
use key_paths_core::readable_keypath;
use key_paths_core::{readable_keypath, ReadableKeyPath};

#[derive(Debug)]
struct User {
Expand All @@ -82,13 +80,31 @@ struct User {

fn main() {
let users = vec![
User { name: "Akash".into(), age: 25 },
User { name: "Soni".into(), age: 30 },
User { name: "Neha".into(), age: 20 },
User {
name: "Akash".into(),
age: 25,
},
User {
name: "Soni".into(),
age: 30,
},
User {
name: "Neha".into(),
age: 20,
},
];

// Read-only keypath
// let name_key = ReadableKeyPath::new(|u: &User| &u.name);
let name_key = readable_keypath!(User, name);

// Writable keypath
// let age_key = WritableKeyPath::new(
// |u: &User| &u.age,
// |u: &mut User| &mut u.age,
// );
// let age_key = writable_keypath!(User, age);

println!("Names:");
for name in name_key.iter(&users) {
println!("{}", name);
Expand All @@ -101,10 +117,7 @@ fn main() {
### 3. Writable KeyPaths

```rust
use key_paths_core::writable_keypath;
use key_paths_core::WritableKeyPath;
use key_paths_core::Readable;
use key_paths_core::Writable;
use key_paths_core::{writable_keypath, WritableKeyPath};

#[derive(Debug)]
struct User {
Expand All @@ -114,29 +127,142 @@ struct User {

fn main() {
let mut users = vec![
User { name: "Akash".into(), age: 25 },
User { name: "Soni".into(), age: 30 },
User { name: "Neha".into(), age: 20 },
User {
name: "Akash".into(),
age: 25,
},
User {
name: "Soni".into(),
age: 30,
},
User {
name: "Neha".into(),
age: 20,
},
];

// Read-only keypath
// let name_key = ReadableKeyPath::new(|u: &User| &u.name);
// let name_key = readable_keypath!(User, name);

// Writable keypath
// let age_key = WritableKeyPath::new(
// |u: & User| & u.age,
// |u: &mut User| &mut u.age,
// );
let age_key = writable_keypath!(User, age);

// println!("Names:");
// for name in name_key.iter(&users) {
// println!("{}", name);
// }

println!("Ages before:");
for age in age_key.iter(&users) {
println!("{}", age);
}

// Mutate agesiter
for age in age_key.iter_mut(&mut users) {
*age += 1;
}

println!("Ages after:");
for age in age_key.iter(&users) {
for age in age_key.iter(&mut users) {
println!("{}", age);
}
}
```

### 4. Composability and failablity
```rust
use key_paths_core::{FailableReadableKeyPath};

#[derive(Debug)]
struct Engine {
horsepower: u32,
}
#[derive(Debug)]
struct Car {
engine: Option<Engine>,
}
#[derive(Debug)]
struct Garage {
car: Option<Car>,
}

fn main() {
let garage = Garage {
car: Some(Car {
engine: Some(Engine { horsepower: 120 }),
}),
};

let kp_car = FailableReadableKeyPath::new(|g: &Garage| g.car.as_ref());
let kp_engine = FailableReadableKeyPath::new(|c: &Car| c.engine.as_ref());
let kp_hp = FailableReadableKeyPath::new(|e: &Engine| Some(&e.horsepower));

// Compose: Garage -> Car -> Engine -> horsepower
let kp = kp_car.compose(kp_engine).compose(kp_hp);

let kp2 = FailableReadableKeyPath::new(|g: &Garage| {
g.car
.as_ref()
.and_then(|c| c.engine.as_ref())
.and_then(|e| Some(&e.horsepower))
});

if let Some(hp) = kp.try_get(&garage) {
println!("{hp:?}");
}

if let Some(hp) = kp2.try_get(&garage) {
println!("{hp:?}");
}

println!("{garage:?}");
}
```
### 4. Mutablity
```rust
use key_paths_core::{FailableWritableKeyPath};

#[derive(Debug)]
struct Engine {
horsepower: u32,
}
#[derive(Debug)]
struct Car {
engine: Option<Engine>,
}
#[derive(Debug)]
struct Garage {
car: Option<Car>,
}

fn main() {
let mut garage = Garage {
car: Some(Car {
engine: Some(Engine { horsepower: 120 }),
}),
};

let kp_car = FailableWritableKeyPath::new(|g: &Garage| g.car.as_ref(), |g: &mut Garage| g.car.as_mut());
let kp_engine = FailableWritableKeyPath::new(|c: &Car| c.engine.as_ref(), |c: &mut Car| c.engine.as_mut());
let kp_hp = FailableWritableKeyPath::new(|e: &Engine| Some(&e.horsepower), |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.try_get_mut(&mut garage) {
*hp = 200;
}

println!("{garage:?}");
}
```

---

## 🔗 Helpful Links & Resources
Expand All @@ -160,8 +286,8 @@ fn main() {

## 🛠 Roadmap

* [ ] `zip` support for combining multiple key paths (Upcoming).
* [ ] Derive macros for automatic KeyPath generation.
* [ ] `compose` support for combining multiple key paths.
* [ ] Derive macros for automatic KeyPath generation (Upcoming).
* [ ] Nested struct & enum traversal.
* [ ] Optional chaining (`User?.profile?.name`).

Expand Down
43 changes: 43 additions & 0 deletions examples/basics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use key_paths_core::*;

#[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(),
};

// Define readable and writable keypaths.
let size_kp: ReadableKeyPath<Rectangle, Size> = ReadableKeyPath::new(|r: &Rectangle| &r.size);
let width_kp: ReadableKeyPath<Size, u32> = ReadableKeyPath::new(|s: &Size| &s.width);

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

// Alternatively, define them directly:
let width_direct: ReadableKeyPath<Rectangle, u32> =
ReadableKeyPath::new(|r: &Rectangle| &r.size.width);
println!("Width: {}", width_direct.get(&rect));

// Writable keypath for modifying fields:
let width_mut: WritableKeyPath<Rectangle, u32> = WritableKeyPath::new(
|r: &Rectangle| &r.size.width,
|r: &mut Rectangle| &mut r.size.width,
);
*(width_mut.get_mut)(&mut rect) = 100;
println!("Updated rectangle: {:?}", rect);
}
56 changes: 56 additions & 0 deletions examples/compose.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use key_paths_core::FailableReadableKeyPath;

#[derive(Debug)]
struct Engine {
horsepower: u32,
}

#[derive(Debug)]
struct Car {
engine: Option<Engine>,
}

#[derive(Debug)]
struct Garage {
car: Option<Car>,
}

#[derive(Debug)]
struct City {
garage: Option<Garage>,
}

fn main() {
let city = City {
garage: Some(Garage {
car: Some(Car {
engine: Some(Engine { horsepower: 250 }),
}),
}),
};

let city_hp2 = FailableReadableKeyPath::new(|c: &City| {
c.garage
.as_ref()
.and_then(|g| g.car.as_ref())
.and_then(|car| car.engine.as_ref())
.and_then(|e| Some(&e.horsepower)) // ✅ removed the extra Some(...)
});

println!("Horsepower = {:?}", (city_hp2.get)(&city));

// compose example ----
// compose keypath together

let city_garage = FailableReadableKeyPath::new(|c: &City| c.garage.as_ref());
let garage_car = FailableReadableKeyPath::new(|g: &Garage| g.car.as_ref());
let car_engine = FailableReadableKeyPath::new(|c: &Car| c.engine.as_ref());
let engine_hp = FailableReadableKeyPath::new(|e: &Engine| Some(&e.horsepower));

let city_hp = city_garage
.compose(garage_car)
.compose(car_engine)
.compose(engine_hp);

println!("Horsepower = {:?}", (city_hp.get)(&city));
}
5 changes: 2 additions & 3 deletions examples/enum_keypath_example.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use key_paths_core::EnumKeyPath;
use key_paths_core::enum_keypath;

#[derive(Debug)]
Expand All @@ -18,18 +19,16 @@ enum SomeOtherStatus {
Inactive,
}


fn main() {
// ---------- EnumPath ----------
let cp = enum_keypath!(Status::Active(User));
let cp2 = enum_keypath!(Status::Inactive(()));


let cp3 = enum_keypath!(SomeOtherStatus::Active(String));
if let Some(x) = cp3.extract(&SomeOtherStatus::Active("Hello".to_string())) {
println!("Active: {:?}", x);
}

let cp4 = enum_keypath!(SomeOtherStatus::Inactive);
if let Some(x) = cp4.extract(&SomeOtherStatus::Inactive) {
println!("Inactive: {:?}", x);
Expand Down
Loading