Skip to content

codefonsi/rust-key-paths

Repository files navigation

πŸ”‘ 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

[dependencies]
key-paths-core = "1.6.0"
key-paths-derive = "1.0.8"

🎯 Choose Your Macro

#[derive(Keypath)] - Simple & Beginner-Friendly

  • One method per field: field_name()
  • Smart keypath selection: Automatically chooses readable or failable readable based on field type
  • No option chaining: Perfect for beginners and simple use cases
  • Clean API: Just call Struct::field_name() and you're done!
use key_paths_derive::Keypath;

#[derive(Keypath)]
struct User {
    name: String,           // -> User::name() returns readable keypath
    email: Option<String>,  // -> User::email() returns failable readable keypath
}

// Usage
let user = User { name: "Alice".into(), email: Some("alice@example.com".into()) };
let name_keypath = User::name();
let email_keypath = User::email();
let name = name_keypath.get(&user);        // Some("Alice")
let email = email_keypath.get(&user);      // Some("alice@example.com")

#[derive(Keypaths)] - Advanced & Feature-Rich

  • Multiple methods per field: field_r(), field_w(), field_fr(), field_fw(), field_o(), field_fo()
  • Full control: Choose exactly which type of keypath you need
  • Option chaining: Perfect for intermediate and advanced developers
  • Comprehensive: Supports all container types and access patterns
use key_paths_derive::Keypaths;

#[derive(Keypaths)]
struct User {
    name: String,
    email: Option<String>,
}

// Usage - you choose the exact method
let user = User { name: "Alice".into(), email: Some("alice@example.com".into()) };
let name_keypath = User::name_r();
let email_keypath = User::email_fr();
let name = name_keypath.get(&user);      // Some("Alice") - readable
let email = email_keypath.get(&user);   // Some("alice@example.com") - failable readable

Widely used - Deeply nested struct

use key_paths_derive::{Casepaths, Keypaths};

#[derive(Debug, Keypaths)]
struct SomeComplexStruct {
    scsf: Option<SomeOtherStruct>,
}


#[derive(Debug, Keypaths)]
struct SomeOtherStruct {
    sosf: Option<OneMoreStruct>,
}

#[derive(Debug, Keypaths)]
struct OneMoreStruct {
    omsf: Option<String>,
    omse: Option<SomeEnum>,
}

#[derive(Debug, Casepaths)]
enum SomeEnum {
    A(String),
    B(DarkStruct),
}

#[derive(Debug, Keypaths)]
struct DarkStruct {
    dsf: Option<String>,
}


impl SomeComplexStruct {
    fn new() -> Self {
        Self {
            scsf: Some(SomeOtherStruct {
                sosf: Some(OneMoreStruct {
                    omsf: Some(String::from("no value for now")),
                    omse: Some(SomeEnum::B(DarkStruct {
                        dsf: Some(String::from("dark field")),
                    })),
                }),
            }),
        }
    }
}


fn main() {
    let dsf_kp = 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();
    
    if let Some(omsf) = dsf_kp.get_mut(&mut instance) {
        *omsf = String::from("This is changed πŸ––πŸΏ");
        println!("instance = {:?}", instance);

    }
}

Recommendation: Start with #[derive(Keypath)] for simplicity, upgrade to #[derive(Keypaths)] when you need more control!

Keypath vs Keypaths - When to Use Which?

Feature #[derive(Keypath)] #[derive(Keypaths)]
API Complexity Simple - one method per field Advanced - multiple methods per field
Learning Curve Beginner-friendly Requires understanding of keypath types
Container Support Basic containers only Full container support including Result, Mutex, RwLock, Wea****k
Option Chaining No - smart selection only Yes - full control over failable vs non-failable
Writable Access Limited Full writable support
Use Case Simple field access, beginners Complex compositions, advanced users

When to use Keypath:

  • You're new to keypaths
  • You want simple, clean field access
  • You don't need complex option chaining
  • You're working with basic types

When to use Keypaths:

  • You need full control over keypath types
  • You're composing complex nested structures
  • You need writable access to fields
  • You're working with advanced container types

πŸš€ Examples

See examples/ for many runnable samples. Below are a few highlights.

Quick Start - Simple Keypaths Usage

use key_paths_derive::Keypath;

#[derive(Keypath)]
struct User {
    name: String,
    age: u32,
    email: Option<String>,
}

fn main() {
    let user = User {
        name: "Alice".to_string(),
        age: 30,
        email: Some("alice@example.com".to_string()),
    };

    // Access fields using keypaths
    let name_keypath = User::name();
    let age_keypath = User::age();
    let email_keypath = User::email();
    
    let name = name_keypath.get(&user);        // Some("Alice")
    let age = age_keypath.get(&user);          // Some(30)
    let email = email_keypath.get(&user);      // Some("alice@example.com")

    println!("Name: {:?}", name);
    println!("Age: {:?}", age);
    println!("Email: {:?}", email);
}

πŸ“¦ Container Adapters & References (NEW!)

KeyPaths now support smart pointers, containers, and references via adapter methods:

Smart Pointer Adapters

Use .for_arc(), .for_box(), or .for_rc() to adapt keypaths for wrapped types:

use key_paths_derive::Keypaths;
use std::sync::Arc;

#[derive(Keypaths)]
struct Product {
    name: String,
    price: f64,
}

let products: Vec<Arc<Product>> = vec![
    Arc::new(Product { name: "Laptop".into(), price: 999.99 }),
];

// Adapt keypath to work with Arc<Product>
let price_path = Product::price().for_arc();

let affordable: Vec<&Arc<Product>> = products
    .iter()
    .filter(|p| price_path.get(p).map_or(false, |&price| price < 100.0))
    .collect();

Reference Support

Use .get_ref() and .get_mut_ref() for collections of references:

use key_paths_derive::Keypaths;

#[derive(Keypaths)]
struct Product {
    name: String,
    price: f64,
}

let products: Vec<&Product> = hashmap.values().collect();
let price_path = Product::price();

for product_ref in &products {
    if let Some(&price) = price_path.get_ref(product_ref) {
        println!("Price: ${}", price);
    }
}

Supported Adapters:

  • .for_arc() - Works with Arc<T> (read-only)
  • .for_box() - Works with Box<T> (read & write)
  • .for_rc() - Works with Rc<T> (read-only)
  • .get_ref() - Works with &T references
  • .get_mut_ref() - Works with &mut T references

Examples:

Documentation: See CONTAINER_ADAPTERS.md and REFERENCE_SUPPORT.md


🌟 Showcase - Crates Using rust-key-paths

The rust-key-paths library is being used by several exciting crates in the Rust ecosystem:


πŸ”— Helpful Links & Resources


πŸ’‘ 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 (Keypaths, Keypaths, Casepaths)
  • Optional chaining with failable keypaths
  • Smart pointer adapters (.for_arc(), .for_box(), .for_rc())
  • Container support for Result, Mutex, RwLock, Weak, and collections
  • Helper derive macros (ReadableKeypaths, WritableKeypaths)
  • [] Derive macros for complex multi-field enum variants

πŸ“œ License

  • Mozilla Public License 2.0

About

ReadableKeyPath and WritableKeyPath for struct and enums in Rust

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages