rust-key-paths 3.4.0

Keypaths for Rust: Static dispatch implementation (rust-keypaths) and legacy dynamic dispatch (key-paths-core). Type-safe, composable access to nested data structures.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# πŸ”‘ KeyPaths in Rust

Key paths provide a **safe, composable way to access and modify nested data** in Rust.
Inspired by **KeyPath and Functional Lenses** system, this feature rich crate lets you work with **struct fields** and **enum variants** as *first-class values*.

### Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
rust-key-paths = "3.4.0"
key-paths-derive = "3.3.0"
# Optional: trait-only contracts (pulled in by rust-key-paths 3.1+)
# key-paths-core = "2.0"
```

`rust-key-paths` does **not** depend on [`rust-elm`](./rust-elm/) β€” that relationship runs the other way. Add `rust-elm` only if you want the Elm/TCA store:

```toml
[dependencies]
rust-elm = "0.5.0"
rust-key-paths = "3.4.0"
key-paths-derive = "3.3.0"
```

### Latest releases

| Crate | Version | Notes |
|-------|---------|--------|
| [`key-paths-core`](https://crates.io/crates/key-paths-core) | **2.1.0** | `#![no_std]` traits; `FieldDiff` + `hash_value` for per-field change signals |
| [`rust-key-paths`](https://crates.io/crates/rust-key-paths) | **3.4.0** | `Kp`, locks, HOF; `RefKpTrait` on `Kp` |
| [`key-paths-derive`](https://crates.io/crates/key-paths-derive) | **3.3.0** | `#[derive(Kp)]`, `#[derive(Cp)]`, `#[derive(FieldDiff)]` |
| [`rust-elm`](https://crates.io/crates/rust-elm) | **0.5.0** | Zero-copy `StateBinding`, `subscribe_changes`, scoped field signals |
| [`rust_identified_vec`](https://crates.io/crates/rust_identified_vec) | **0.1.2** | Identified collections (used by `rust-elm`) |
| [`rust_dependencies`](https://crates.io/crates/rust_dependencies) | **0.1.2** | Typed DI for effects (used by `rust-elm`) |

#### 3.4.0 / 2.1.0 / 3.3.0 / 0.5.0

- **`FieldDiff`** in `key-paths-core` β€” per-field hashing for change signals without cloning whole state.
- **`#[derive(FieldDiff)]`** in `key-paths-derive` 3.3.0 β€” generates `{Struct}Field` path enum + `field_hashes`.
- **`rust-elm` 0.5.0** β€” `StateBinding` / keypath projection, `subscribe_changes`, `ScopedChangeSubscriber`; see [binding.md](./rust-elm/book/binding.md).

#### 3.3.1 / 2.0.4 / 0.3.0

- **`RefKpTrait`** in `key-paths-core` β€” HRTB `focus` / `focus_mut` for local borrows; `rust-key-paths` `Kp` implements it via `get_ref` / `get_mut_ref`.
- **`rust-elm` 0.3.0** β€” `RuntimeConfig` (bus capacity, Tokio workers, reducer thread name); scope/store use `RefKpTrait` directly (`StateLens` removed); `StoreTask::finish_with_timeout`; pain/ISO 20022 validation example.
- **`key-paths-derive` 3.2.0** β€” unchanged; still compatible.

#### 3.3.0 / 2.0.3 / 3.2.0

- **`EnumKp` / `EnumValueKpType`** β€” casepaths (prisms) for enum variants: extract + embed.
- **`EnumKp::get_ref`**, **`EnumKp::then` / `chain`** β€” compose nested casepaths.
- **`#[derive(Cp)]`** on enums and structs in `key-paths-derive` 3.2.0.
- **`rust-elm` 0.2.0** β€” separate crate: WebSocket subs, runtime features, ecommerce example (depends on this crate).
- **`rust_identified_vec` 0.1.2**, **`rust_dependencies` 0.1.2** β€” patch releases.

#### 3.1.1 / 2.0.1 / 3.0.2 (documentation)

- README guides for adapting keypaths via `Readable` / `Writable` (including `#[derive(Kp)]`).
- Confirmed **`key-paths-derive` is not broken by `key-paths-core` 2.x** β€” upgrade **`rust-key-paths` to 3.1.1** in your app.

#### 3.1.0 / 2.0.0 (initial trait stack)

- **`key-paths-core` 2.0** β€” trait-only rewrite (`Readable`, `Writable`, `KpTrait::then`, …).
- **`rust-key-paths` 3.1** β€” depends on core 2.x; re-exports traits on `Kp`.

### Generic APIs with `Readable` / `Writable`

You can accept any keypath (including derived ones) in generic code:

```rust
use key_paths_derive::Kp;
use rust_key_paths::Readable;

#[derive(Kp)]
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");
    }
}

fn main() {
    let payload = BigPayload2 {
        emergency_contact: Some("555-0100".into()),
    };
    test(&payload, &BigPayload2::emergency_contact());
}
```

See [key-paths-core/README.md](./key-paths-core/README.md) and [key-paths-derive/README.md](./key-paths-derive/README.md) for migration from `key-paths-core` 1.7 and derive compatibility details.

### Basic usage

```rust
use std::sync::Arc;
use key_paths_derive::Kp;

#[derive(Debug, Kp)]
struct SomeComplexStruct {
    scsf: Option<SomeOtherStruct>,
    scfs2: Arc<std::sync::RwLock<SomeOtherStruct>>,
}

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

#[derive(Debug, Kp)]
enum SomeEnum {
    A(String),
    B(Box<DarkStruct>),
}

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

#[derive(Debug, Kp)]
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(Box::new(DarkStruct {
                        dsf: Some(String::from("dark field")),
                    }))),
                }),
            }),
            scfs2: Arc::new(std::sync::RwLock::new(SomeOtherStruct {
                sosf: Some(OneMoreStruct {
                    omsf: Some(String::from("no value for now")),
                    omse: Some(SomeEnum::B(Box::new(DarkStruct {
                        dsf: Some(String::from("dark field")),
                    }))),
                }),
            })),
        }
    }
}
fn main() {
    let mut instance = SomeComplexStruct::new();

    SomeComplexStruct::scsf()
        .then(SomeOtherStruct::sosf())
        .then(OneMoreStruct::omse())
        .then(SomeEnum::b())
        .then(DarkStruct::dsf())
        .get_mut(&mut instance).map(|x| {
        *x = String::from("πŸ––πŸΏπŸ––πŸΏπŸ––πŸΏπŸ––πŸΏ");
    });

    println!("instance = {:?}", instance.scsf.unwrap().sosf.unwrap().omse.unwrap());
    // output - instance = B(DarkStruct { dsf: Some("πŸ––πŸΏπŸ––πŸΏπŸ––πŸΏπŸ––πŸΏ") })
}
```

### Composing keypaths

Chain through nested structures with `then()`:

```rust
#[derive(Kp)]
struct Address { street: String }

#[derive(Kp)]
struct Person { address: Box<Address> }

let street_kp = Person::address().then(Address::street());
let street = street_kp.get(&person);  // Option<&String>
```

### Casepaths (enum prisms)

Keypaths focus **struct fields**. **Casepaths** focus **enum variants** and support both **extraction** (read the payload when the enum matches) and **embedding** (wrap a payload in the variant).

#### Types

| Type | Role |
|------|------|
| [`EnumKp`](https://docs.rs/rust-key-paths/latest/rust_key_paths/struct.EnumKp.html) | Generic casepath (extractor `Kp` + embedder) |
| [`EnumKpType<'a, E, V>`](https://docs.rs/rust-key-paths/latest/rust_key_paths/type.EnumKpType.html) | Common alias: reference-shaped extract, `fn` embedder. Single-payload variants. |
| [`EnumValueKpType<'a, E, P>`](https://docs.rs/rust-key-paths/latest/rust_key_paths/type.EnumValueKpType.html) | Multi-field variants: payload `P` is extracted **by value** (clone), typically a tuple. |

#### Derive (`#[derive(Cp)]`)

The fastest path β€” no manual `variant_of` wiring:

```rust
use key_paths_derive::{Cp, Kp};

#[derive(Clone, Kp, Cp)]
enum Action {
    Child(ChildAction),
    Card(String, String),
    Tick,
}

let child = Action::child_cp();  // EnumKpType<'static, Action, ChildAction>
let embedded = child.embed(ChildAction::Inc);
assert_eq!(child.get_ref(&embedded), Some(&ChildAction::Inc));

let card = Action::card_cp();    // EnumValueKpType<'static, Action, (String, String)>
let payment = card.embed(("4242".into(), "123".into()));
assert_eq!(card.get(&payment), Some(("4242".into(), "123".into())));
```

Full variant matrix: [key-paths-derive/README.md β€” Casepaths (`Cp`)](./key-paths-derive/README.md#casepaths-cp).

#### Factory helpers

```rust
use rust_key_paths::{enum_ok, enum_err, enum_some, variant_of, EnumKp, Kp};

// Option / Result built-ins
let some_kp = enum_some::<String>();
assert_eq!(some_kp.get(&Some("x".into())), Some(&"x".to_string()));

// Custom enum variant (same as #[derive(Cp)] for single-field variants)
enum Payment { Cash(u32), Card(String) }
let cash_kp = variant_of(
    |p: &Payment| match p { Payment::Cash(n) => Some(n), _ => None },
    |p: &mut Payment| match p { Payment::Cash(n) => Some(n), _ => None },
    |n: u32| Payment::Cash(n),
);
assert_eq!(cash_kp.embed(10), Payment::Cash(10));

// Pair #[derive(Kp)] variant accessor with constructor
// Action::child().with_embed(Action::Child)
```

#### `Kp` vs casepath

| | `#[derive(Kp)]` on enum | `#[derive(Cp)]` / `EnumKp` |
|--|-------------------------|----------------------------|
| Struct fields | βœ… `field()` lens | β€” |
| Enum variant extract | βœ… `variant()` | βœ… via extractor |
| Enum variant embed | ❌ | βœ… `embed(payload)` |
| Typical use | Chaining `.then()` through enums | Scoping actions, prisms, routing |

#### Four-level nested actions

Each level is a single-payload variant. Compose casepaths with **`.then()`** / **`.chain()`** β€” one chain for both extract and embed (mirroring Swift CasePaths `append`):

```rust
#[derive(Clone, Copy, Kp, Cp)]
enum RootAction { App(AppAction) }
#[derive(Clone, Copy, Kp, Cp)]
enum AppAction { Panel(PanelAction) }
#[derive(Clone, Copy, Kp, Cp)]
enum PanelAction { Widget(WidgetAction) }
#[derive(Clone, Copy, Kp, Cp)]
enum WidgetAction { Tap, Submit }

let to_widget = RootAction::app_cp()
    .then(AppAction::panel_cp())
    .chain(PanelAction::widget_cp());

to_widget.get_ref(&root);              // extract leaf
to_widget.embed(WidgetAction::Submit); // embed leaf β†’ RootAction
```

In [`rust-elm`](./rust-elm/), each scope/reducer layer still uses **one** casepath per step (`Action::child_cp()`); four levels means four nested scopes. For ad-hoc routing without scopes, use the composed chain above. See [`examples/casepath.rs`](./examples/casepath.rs).

### Partial and Any keypaths

Use `#[derive(Pkp, Akp)]` (requires `Kp`) to get type-erased keypath collections:

- **PKp** – `partial_kps()` returns `Vec<PKp<Self>>`; value type erased, root known
- **AKp** – `any_kps()` returns `Vec<AKp>`; both root and value type-erased for heterogeneous collections

Filter by `value_type_id()` / `root_type_id()` and read with `get_as()`. For writes, dispatch to the typed `Kp` (e.g. `Person::name()`) based on TypeId.

See examples: `pkp_akp_filter_typeid`, `pkp_akp_read_write_convert`.

### Features

| Feature | Description |
|---------|-------------|
| `parking_lot` | Use `parking_lot::Mutex` / `RwLock` instead of `std::sync` |
| `tokio` | Async lock support (`tokio::sync::Mutex`, `RwLock`) |
| `arcswap` | [`arc-swap`](https://docs.rs/arc-swap) (`Arc<ArcSwap<T>>`, `Arc<ArcSwapOption<T>>`) via [`SyncKp`](https://docs.rs/rust-key-paths/latest/rust_key_paths/sync_kp/struct.SyncKp.html) |
| `pin_project` | Enable `#[pin]` field support for pin-project compatibility |

### More examples

```bash

```

---

## Supported containers

The `#[derive(Kp)]` macro (from `key-paths-derive`) generates keypath accessors for these wrapper types:

| Container | Access | Notes |
|-----------|--------|-------|
| `Option<T>` | `field()` | Unwraps to inner type |
| `Box<T>` | `field()` | Derefs to inner |
| `Pin<T>`, `Pin<Box<T>>` | `field()`, `field_inner()` | Container + inner (when `T: Unpin`) |
| `Rc<T>`, `Arc<T>` | `field()` | Derefs; mut when unique ref |
| `Vec<T>` | `field()`, `field_at(i)` | Container + index access |
| `HashMap<K,V>`, `BTreeMap<K,V>` | `field_at(k)` | Key-based access |
| `HashSet<T>`, `BTreeSet<T>` | `field()` | Container identity |
| `VecDeque<T>`, `LinkedList<T>`, `BinaryHeap<T>` | `field()`, `field_at(i)` | Index where applicable |
| `Result<T,E>` | `field()` | Unwraps `Ok` |
| `Cow<'_, T>` | `field()` | `as_ref` / `to_mut` |
| `Option<Cow<'_, T>>` | `field()` | Optional Cow unwrap |
| `std::sync::Mutex<T>`, `std::sync::RwLock<T>` | `field()` | Container (use `SyncKp` for lock-through) |
| `Arc<Mutex<T>>`, `Arc<RwLock<T>>` | `field()`, `field_kp()` / `field()` as `SyncKp` | Lock-through via `SyncKp` |
| `Arc<arcswap::ArcSwap<T>>`, `Arc<arcswap::ArcSwapOption<T>>` | `field_kp()` / `field()` as `SyncKp` | arcswap feature; use `arcswap` dependency key (see below) |
| `tokio::sync::Mutex`, `tokio::sync::RwLock` | `field_async()` | Async lock-through (tokio feature) |
| `parking_lot::Mutex`, `parking_lot::RwLock` | `field()`, `field_lock()` | parking_lot feature |

Nested combinations (e.g. `Option<Box<T>>`, `Option<Vec<T>>`, `Vec<Option<T>>`) are supported.

### `arcswap` (optional): atomically swappable `Arc`

Enable **`arcswap`** on `rust-key-paths` and add the same dependency key in your crate so generated paths resolve:

```toml
[dependencies]
rust-key-paths = { version = "3.2.0", features = ["arcswap"] }
arcswap = { package = "arc-swap", version = "1.9" }
```

**When to use [`ArcSwap`](https://docs.rs/arc-swap/latest/arc_swap/struct.ArcSwap.html) instead of `RwLock<Arc<T>>`:** you reload or publish whole snapshots (`store` / `swap` / `rcu`) and many threads read the current snapshot most of the time. Reads use `load()` (default strategy: low-latency, lock-free snapshots) instead of contending on a reader–writer lock. Prefer a **`static`** or **`LazyLock`** holding an `ArcSwap` when a single global pointer is enough; wrap in **`Arc<ArcSwap<T>>`** when the swap container is created at runtime and shared across threads (the outer `Arc` is only for sharing the container; hot-path reads touch the inner atomic pointer, not the `Arc` refcount).

**When not to:** you need a true in-place `&mut T` through a lock for arbitrary mutation of `T` inside the guard. `ArcSwap` stores an `Arc<T>`; updates replace the pointer. Use `store` / `rcu` at the call site for writes.

**Chaining:** compose the full lock path on the first `SyncKp` with `.then(…)` / `.then_sync(…)` (see `examples/box_keypath_arcswap.rs`). Import [`ChainExt`](https://docs.rs/rust-key-paths/latest/rust_key_paths/trait.ChainExt.html) for `Kp::then_sync`. Nested `then_sync` from the crate root can infer a `'static` root in some compositions; if you hit that, call an inner `SyncKp` from a `&` to the inner struct (same example).

### pin_project `#[pin]` fields (optional feature)

When using [pin-project](https://docs.rs/pin-project), mark pinned fields with `#[pin]`. The derive generates:

| `#[pin]` field type | Access | Notes |
|---------------------|--------|-------|
| Plain (e.g. `i32`) | `field()`, `field_pinned()` | Pinned projection via `this.project()` |
| `Future` | `field()`, `field_pinned()`, `field_await()` | Poll through `Pin<&mut Self>` |
| `Box<dyn Future<Output=T>>` | `field()`, `field_pinned()`, `field_await()` | Same for boxed futures |

Enable with `pin_project` feature and add `#[pin_project]` to your struct:

```rust
#[pin_project]
#[derive(Kp)]
struct WithPinnedFuture {
    fair: bool,
    #[pin]
    fut: Pin<Box<dyn Future<Output = String> + Send>>,
}
```

Examples: `pin_project_example`, `pin_project_fair_race` (FairRaceFuture use case).

## Performance: `box_keypath` benchmark

Benchmark file: `benches/box_keypath_bench.rs`

Run with:

```bash
cargo bench --bench box_keypath_bench
```

### Read path (`scsf -> sosf -> omse -> B -> dsf`)

| Variant | Time (approx) |
|---------|---------------|
| keypath | 996.46-997.18 ps |
| unwrap | 944.10-946.59 ps |
| as_ref().map | 996.31-997.39 ps |
| `?` operator | 996.33-997.24 ps |

### Write path (`scsf -> sosf -> omse -> B -> dsf`)

| Variant | Time (approx) |
|---------|---------------|
| keypath | 147.44-149.09 ns |
| unwrap | 143.13-145.02 ns |
| as_ref().map | 141.04-142.65 ns |
| `?` operator | 141.41-150.25 ns |

These numbers are from Criterion's reported confidence ranges on this machine. In this benchmark, keypaths are very close to direct traversal for reads and only slightly slower for writes.

### Keypath size

From `examples/box_keypath.rs`, the composed keypath prints:

```text
size of kp = 0
```

So this composed `kp` is zero-sized (no captured runtime state).

## Performance: `arcswap_keypath` benchmark

Benchmark file: `benches/arcswap_keypath_bench.rs` (requires `--features arcswap`).

```bash
cargo bench --bench arcswap_keypath_bench --features arcswap
```

### Read path (`scsf` β†’ `ArcSwap` load β†’ `omse` β†’ `B` β†’ `dsf`)

Compared to `load_full()` plus a manual `match` on the loaded `OneMoreStruct` (which clones the inner `Arc` on every read), the composed keypath uses `load()` under the hood and stays on the snapshot for the rest of the chain.

| Variant | Time (approx, `--quick` run on one machine) |
|---------|-----------------------------------------------|
| `keypath_then_sync` | 37.8–38.7 ns |
| `load_full_manual` | 115–119 ns |

Your numbers will vary by CPU and optimization level; treat this as a sanity check that keypath traversal stays in the same ballpark as a small manual load, while **`load_full` + clone** is heavier by design when you need an owned `Arc`.

---

### Skills guide (AI assistants & tooling)

Usage-focused patternsβ€”versions/features, `#[derive(Kp)]`, chaining, sync/async locks, deep nesting, migrating from `Option` / Java-style getter chainsβ€”live in **[`Skills.md`](./Skills.md)** at the **repository root**.

**Paths to reference:** `Skills.md` (repo root), or `./Skills.md` from this README’s directory. Point Cursor **Rules**, **Agent Skills**, or similar project instructions at that file so assistants can resolve the path and include it in context.

---

## πŸ“œ License

* Mozilla Public License 2.0