key-paths-core 2.0.1

Trait-only keypath surface (Readable, Writable, KpTrait, AccessorTrait) for building custom keypath libraries.
Documentation

key-paths-core

Trait-only keypath contracts for Rust. No proc-macros, no locks, no async runtime — only the interfaces your library can implement so callers share a common read/write surface.

Use this crate when you want to build a custom keypath library (or integrate keypaths into an existing framework) without pulling in rust-key-paths.

For a full reference implementation (derive macros, Kp, sync/async locks, composition), see rust-key-paths in the same repository.

Release notes

2.0.1

  • README: generic Readable / Writable integration guide and #[derive(Kp)] compatibility notes.

2.0.0

  • Trait-only surfaceReadable, Writable, KeyPath, KpTrait (with then), AccessorTrait, KeyPathValueTarget.
  • #![no_std] — no proc-macros, locks, or async in this crate.
  • Breaking vs 1.7 — the old KeyPaths enum and container helpers are removed; stay on 1.7 or use rust-key-paths 3.x.

key-paths-derive is unaffected as a crate (it does not depend on key-paths-core). Generated #[derive(Kp)] code still targets rust-key-paths ≥ 3.1, which implements these traits on Kp. See key-paths-derive README.

Traits

Trait Role
Readable<Root, Value> Getter path: root → optional Value
Writable<MutRoot, MutValue> Setter path: mut root → optional mutable Value
KeyPath<Root, Value, MutRoot, MutValue> Marker: both read and write
KpTrait<R, V, Root, Value, MutRoot, MutValue> Above + TypeId helpers + then
KeyPathValueTarget Maps &T / &mut TT for generic chaining
AccessorTrait Optional-root and or_else helpers (default methods)

Architecture: adapting keypaths in your crate

1. Pick root and value types

  • Root — what the caller passes into get (often &MyStruct or &mut MyStruct).
  • Value — what the keypath returns on success (often &Field or &mut Field).
  • MutRoot / MutValue — usually the same shapes as Root/Value for mutation paths.

Keep logical types R and V in mind when you implement [KpTrait] (for TypeId and documentation).

2. Implement Readable and Writable or use key-paths-derive macro

use key_paths_core::{Readable, Writable};

pub struct NameKp;

impl Readable<&Person, &str> for NameKp {
    fn get(&self, root: &Person) -> Option<&str> {
        Some(&root.name)
    }
}

impl Writable<&mut Person, &mut str> for NameKp {
    fn set(&self, root: &mut Person) -> Option<&mut str> {
        Some(&mut root.name)
    }
}

2b. Generic functions over any keypath (including #[derive(Kp)])

Bound on Readable / Writable instead of a concrete Kp type. This works with hand-written keypaths and with paths from key-paths-derive (via rust-key-paths, which implements the traits on Kp):

use key_paths_core::Readable;
// With derive: use key_paths_derive::Kp; use rust_key_paths::Readable;

struct BigPayload2 {
    emergency_contact: Option<String>,
}

fn test<'p, G>(payload: &'p BigPayload2, g: G)
where
    G: Readable<&'p BigPayload2, &'p String>,
{
    if let Some(emg_contact) = g.get(payload) {
        println!("there value = {:?}", emg_contact);
    } else {
        println!("not there");
    }
}

// Call site (after #[derive(Kp)] on BigPayload2):
// test(&payload, BigPayload2::emergency_contact());
  • Root / Value in the trait are the types you pass to get and receive on success (often &Struct and &Field).
  • Use Writable<&'p mut Root, &'p mut Value> for mutation helpers the same way.

3. Optional: AccessorTrait

Blanket-style ergonomics without new methods on your type:

use key_paths_core::{AccessorTrait, Readable, Writable};

impl AccessorTrait<&Person, &str, &mut Person, &mut str> for NameKp {}

4. Composition with [KpTrait::then]

Implement then on your keypath type; return type Out is inferred at the call site (see rust-key-paths Kp::then for a reference impl).

Lock traversal and async chaining stay in rust-key-paths (SyncKp, AsyncLockKp, ChainExt).

5. Async keypaths

Define async methods on your type (e.g. async fn get(&self, root: Root) -> Option<Value>), and optionally a blocking adapter that implements Readable/Writable for background threads (see rust-key-paths block_async pattern). The core traits stay synchronous so they stay runtime-agnostic.

6. Lock / shared state

Do not fake &mut T through Arc or lock-free snapshots. Either:

  • return owned snapshots / guards from your own API, or
  • use rust-key-paths lock keypaths (SyncKp, AsyncLockKp) that encode the right semantics.

Cargo.toml

[dependencies]
key-paths-core = "2.0"

No default features. #![no_std] with alloc not required.

Migration from 1.x

Version 2.0 is a breaking rewrite: the old dynamic KeyPaths enum and container helpers were removed in favor of these traits. Existing code that used key_paths_core::KeyPaths should stay on 1.x or migrate to rust-key-paths 3.x.

Related crates

Crate Purpose
rust-key-paths Reference Kp, derive, sync/async locks, ChainExt, HOF helpers
key-paths-derive #[derive(Kp)] proc-macro (depends on rust-key-paths, not this crate)