diff --git a/.gitignore b/.gitignore index 01b0d62..f1851d6 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ key-paths-core/.idea/key-paths-core.iml # Cheque dataset (benchmark images) benches/cheq_dataset +keypaths-proc/Cargo.lock diff --git a/1cd b/1cd new file mode 100644 index 0000000..bd7c4b9 --- /dev/null +++ b/1cd @@ -0,0 +1,718 @@ +warning: unused import: `Mutex` + --> src/lib.rs:13:22 + | +13 | use std::sync::{Arc, Mutex}; + | ^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: unused import: `std::sync::Arc` + --> src/async_lock.rs:34:5 + | +34 warning: unused import: `Mutex` + --> src/lib.rs:13:22 + | +13 | use std::sync::{Arc, Mutex}; + | ^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +warning: unused import: `std::sync::Arc` + --> src/async_lock.rs:34:5 + | +34 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: variable does not need to be mutable + --> src/async_lock.rs:477:13 + | +477 | let mut mid_value = self + | ----^^^^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` on by default + +warning: variable does not need to be mutable + --> src/lock.rs:289:41 + | +289 | (self.prev.set)(root).and_then(|mut lock_value| { + | ----^^^^^^^^^^ + | | + | help: remove this `mut` + +| use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: variable does not need to be mutable + --> src/async_lock.rs:477:13 + | +477 | let mut mid_value = self + | ----^^^^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` on by default + +warning: variable does not need to be mutable + --> src/lock.rs:289:41 + | +289 | (self.prev.set)(root).and_then(|mut lock_value| { + | ----^^^^^^^^^^ + | | + | help: remove this `mut` + +warning: `rust-key-paths` (lib) generated 4 warnings (run `cargo fix --lib -p rust-key-paths` to apply 4 suggestions) +warning: `rust-key-paths` (lib) generated 4 warnings (run `cargo fix --lib -p rust-key-paths` to apply 4 suggestions) +warning: method `inject_f32` is never used + --> key-paths-iter/src/kp_gpu.rs:369:8 + | +367 | trait ErasedGpuKp: Send + Sync { + | ----------- method in this trait +368 | fn extract_f32(&self, root: &R) -> Option; +369 | fn inject_f32(&self, root: &mut R, val: f32); + | ^^^^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: method `inject_f32` is never used + --> key-paths-iter/src/kp_gpu.rs:369:8 + | +367 | trait ErasedGpuKp: Send + Sync { + | ----------- method in this trait +368 | fn extract_f32(&self, root: &R) -> Option; +369 | fn inject_f32(&self, root: &mut R, val: f32); + | ^^^^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: `key-paths-iter` (lib) generated 1 warning +warning: `key-paths-iter` (lib) generated 1 warning +warning: unused import: `std::sync::Arc` + --> src/lib.rs:3808:13 + | +3808 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> src/lib.rs:3870:13 + | +3870 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> src/lib.rs:4025:13 + warning: unused import: `std::sync::Arc` + --> src/lib.rs:3808:13 + | +3808 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> src/lib.rs:3870:13 + | +3870 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> src/lib.rs:4025:13 + | +4025 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +| +4025 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `Weak` + --> src/lib.rs:5009:30 + |warning: unused import: `Weak` + --> src/lib.rs:5009:30 + | +5009 | use std::sync::{Arc, Weak}; + | ^^^^ + + +5009 | use std::sync::{Arc, Weak}; + | ^^^^ + +warning: unused variable: `root` + --> src/lock.rs:1755:13 + | +1755 | let root = Root { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_root` + | + = note: `#[warn(unused_variables)]` on by default + +warning: unused variable: `instance2` + --> src/lib.rs:2551:13 + | +2551 | let instance2 = TestKP2::new(); + | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_instance2` + +warning: unused variable: `root` + --> src/lock.rs:1755:13 + | +1755 | let root = Root { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_root` + | + = note: `#[warn(unused_variables)]` on by default + +warning: unused variable: `instance2` + --> src/lib.rs:2551:13 + | +2551 | let instance2 = TestKP2::new(); + | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_instance2` + +warning: unused variable: `kp_a` + --> src/lib.rs:2554:13 + | +2554 | let kp_a = TestKP::a(); + | ^^^^ help: if this is intentional, prefix it with an underscore: `_kp_a` + +warning: variable does not need to be mutable + --> src/lib.rs:2700:13 + | +2700 | let mut none_opt: Option = None; + | ----^^^^^^^^ + | | + | help: remove this `mut` + +warning: unused variable: `kp_a` + --> src/lib.rs:2554:13 + | +2554 | let kp_a = TestKP::a(); + | ^^^^ help: if this is intentional, prefix it with an underscore: `_kp_a` + +warning: variable does not need to be mutable + --> src/lib.rs:2700:13 + | +2700 | let mut none_opt: Option = None; + | ----^^^^^^^^ + | | + | help: remove this `mut` + +warning: unused variable: `arc_shared2`warning: unused variable: `arc_shared2` + --> src/lib.rs:2797:13 + | +2797 | let arc_shared2 = Arc::clone(&arc_shared); + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_arc_shared2` + + + --> src/lib.rs:2797:13 + | +2797 | let arc_shared2 = Arc::clone(&arc_shared); + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_arc_shared2` + +warning: unused variable: `product` + --> src/lib.rs:4742:13warning: unused variable: `product` + --> src/lib.rs:4742:13 + | +4742 | let product = Product { + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_product` + | +4742 | let product = Product { + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_product` + + + +warning: field `data` is never read + --> src/lock.rs:1851:13 + | +1850 | warning: field `data` is never read + --> src/lock.rs:1851:13 + | +1850 | struct Level1 { + | ------ field in this struct +1851 | data: String, + | ^^^^ + | + = struct Level1 { + | ------ field in this struct +1851 | data: String, + | ^^^^ + | + = note: `Level1` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis + = note: `#[warn(dead_code)]` on by default + +note: `Level1` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis + = note: `#[warn(dead_code)]` on by default + +warning: field `data` is never read + --> src/lock.rs:2299:13 + | +2298 | struct PanicOnClone { + | ------------ field in this structwarning: field `data` is never read + --> src/lock.rs:2299:13 + | +2298 | struct PanicOnClone { + | ------------ field in this struct +2299 | data: String, + | ^^^^ + + +2299 | data: String, + | ^^^^ + +warning: method `get_data` is never used + --> src/lock.rs:2309:16 + | +2302 | impl PanicOnClone {warning: method `get_data` is never used + --> src/lock.rs:2309:16 + | +2302 | impl PanicOnClone { + | ----------------- method in this implementation +... +2309 | fn get_data(&self) -> &String { + | ^^^^^^^^ + +warning: field `panic_data` is never read + --> src/lock.rs:2329:13 + | +2328 | struct Level1 { + | ------ field in this struct +2329 | panic_data: PanicOnClone, + | ^^^^^^^^^^ + + + | ----------------- method in this implementation +... +2309 | fn get_data(&self) -> &String { + | ^^^^^^^^ + +warning: field `panic_data` is never read + --> src/lock.rs:2329:13 + | +2328 | struct Level1 { + | ------ field in this struct +2329 | panic_data: PanicOnClone, + | ^^^^^^^^^^ + +warningwarning: field `panic_data2` is never read + --> src/lock.rs:2344:13 + | +2343 | struct Level2 { + | ------ field in this struct +2344 | panic_data2: PanicOnClone, +: field `panic_data2` is never read + --> src/lock.rs:2344:13 + | +2343 | struct Level2 { + | ------ field in this struct +2344 | panic_data2: PanicOnClone, + | ^^^^^^^^^^^ + + | ^^^^^^^^^^^ + +warning: field `data` is never read + --> warning: field `data` is never read + --> src/lock.rs:2404:13 + | +2403 | struct PanicOnClone { + | ------------ field in this struct +src/lock.rs:2404:13 + | +2403 | struct PanicOnClone { + | ------------ field in this struct +2404 | data: Vec, + | ^^^^ + +2404 | data: Vec, + | ^^^^ + +warning: field `panic_data` is never read + warning: field `panic_data` is never read + --> src/lock.rs:2427:13 + | +2426 | struct Mid { + |--> src/lock.rs:2427:13 + | +2426 | struct Mid { + | --- field in this struct +2427 | --- field in this struct +2427 | panic_data: PanicOnClone, + | ^^^^^^^^^^ + + panic_data: PanicOnClone, + | ^^^^^^^^^^ + +warning: field `panic_data` is never read + --> src/lock.rs:2438:13warning: field `panic_data` is never read + --> src/lock.rs:2438:13 + | +2437 | struct Inner { + + | +2437 | struct Inner { + | ----- field in this struct +2438 | panic_data: PanicOnClone, + | ----- field in this struct +2438 | panic_data: PanicOnClone, + | ^^^^^^^^^^ + + | ^^^^^^^^^^ + +warning: field `large_data` is never read + --> warning: field `large_data` is never read + --> src/lock.rs:2494:13 + | +2492 | struct NeverClone { + |src/lock.rs:2494:13 + | +2492 | struct NeverClone { + | ---------- field in this struct +2493 | id: usize, +2494 ---------- field in this struct +2493 | id: usize, +2494 | large_data: Vec, + | ^^^^^^^^^^ + +| large_data: Vec, + | ^^^^^^^^^^ + +warning: field `never_clone1` is never read +warning: field `never_clone1` is never read + --> src/lock.rs:2518:13 + | +2517 | struct Mid { + | --- --> src/lock.rs:2518:13 + | +2517 | struct Mid { + | --- field in this struct +2518 | never_clone1: NeverClone, + | ^^^^^^^^^^^^ field in this struct +2518 | never_clone1: NeverClone, + | ^^^^^^^^^^^^ + + + +warning: field `never_clone2` is never read + --> warning: field `never_clone2` is never read + --> src/lock.rs:2529:13 + | +2528 | struct Inner { + |src/lock.rs:2529:13 + | +2528 | struct Inner { + | ----- field in this struct +2529 | never_clone2: NeverClone, + | ----- field in this struct +2529 | never_clone2: NeverClone, + | ^^^^^^^^^^^^ + + ^^^^^^^^^^^^ + +warning: field `data` is never read + --> src/lock.rs:2785:13 + | +2784 | struct PanicOnClone { + | ------------ field in this struct +2785 | data: String, + | ^^^^ +warning: field `data` is never read + --> src/lock.rs:2785:13 + | +2784 | struct PanicOnClone { + | ------------ field in this struct +2785 | data: String, + | ^^^^ + + +warning: field `panic_data` is never read + --> src/lock.rs:2800:13 +warning: field `panic_data` is never read + --> src/lock.rs:2800:13 + | +2799 | struct Level1 { + | ------ | +2799 | struct Level1 { + | ------ field in this struct +2800 | panic_data: PanicOnClone, + | ^^^^^^^^^^ field in this struct +2800 | panic_data: PanicOnClone, + | ^^^^^^^^^^ + + + +warning: field `panic_data2` is never read + --> src/lock.rs:2811:13 + warning: field `panic_data2` is never read + --> src/lock.rs:2811:13 + | +2810 | struct Level2 { + | ------ field in this struct +2811 | +2810 | struct Level2 { + | ------ field in this struct +2811 | panic_data2: PanicOnClone, + | ^^^^^^^^^^^ + +| panic_data2: PanicOnClone, + | ^^^^^^^^^^^ + +warning: fields `b`, `c`, `d`, and `e` are never read + --> src/lib.rs:2378:9 + | +2376 | struct TestKP { + | ------ fields in this struct +2377 | a: String, +2378 | b: String, + | ^ +2379 | c: std::sync::Arc, + | ^ +2380 | d: std::sync::Mutex, + | ^ +2381 | e: std::sync::Arc>, + | ^ + | + warning: fields `b`, `c`, `d`, and `e` are never read + --> src/lib.rs:2378:9 + | +2376 | struct TestKP { + | ------ fields in this struct +2377 | a: String, +2378 | b: String, + | ^ +2379 | c: std::sync::Arc, + | ^ +2380 | d: std::sync::Mutex, + | ^ +2381 | e: std::sync::Arc>, + | ^ + | + = note: `TestKP` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + += note: `TestKP` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + +warning: associated functions `a_typed` and `c` are never used + --> src/lib.rs:2411:12 + | +2386 | impl TestKP { + | ----------- associated functions in this implementation +... +2411 | fn a_typed() -> Kp< + | ^^^^^^^ +... +2435 | fn c<'a>() -> KpType<'a, TestKP, String> { + | ^ + +warning: field `b` is never read + --> src/lib.rs:2461:9 + | +2459 | struct TestKP2 { + | ------- field in this struct +2460warning: associated functions `a_typed` and `c` are never used + --> src/lib.rs:2411:12 + | +2386 | impl TestKP { + | ----------- associated functions in this implementation +... +2411 | fn a_typed() -> Kp< + | ^^^^^^^ +... +2435 | fn c<'a>() -> KpType<'a, TestKP, String> { + | ^ + +warning: field `b` is never read + --> src/lib.rs:2461:9 + | +2459 | struct TestKP2 { + | ------- field in this struct +2460 | a: String, +2461 | b: std::sync::Arc>, + | ^ + | + | a: String, +2461 | b: std::sync::Arc>, + | ^ + | + = note: `TestKP2` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + + = note: `TestKP2` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + +warning: associated functions `identity_typed` and `b` are never used + --> src/lib.rs:2472:12 + | +2464 | impl TestKP2 { + | ------------ associated functions in this implementation +... +2472 | fn identity_typed() -> Kp< + | ^^^^^^^^^^^^^^ +... +2495 | fn b<'a>() -> KpType<'a, TestKP2, std::sync::Arc>> { + | ^ + +warning: fields `a` and `b` are never read + --> src/lib.rs:2510:9 + | +2509 | struct TestKP3 { + |warning: associated functions `identity_typed` and `b` are never used + --> src/lib.rs:2472:12 + | +2464 | impl TestKP2 { + | ------------ associated functions in this implementation +... +2472 | fn identity_typed() -> Kp< + | ^^^^^^^^^^^^^^ +... +2495 | fn b<'a>() -> KpType<'a, TestKP2, std::sync::Arc>> { + | ^ + +warning: fields `a` and `b` are never read + --> src/lib.rs:2510:9 + | +2509 | struct TestKP3 { + | ------- fields in this struct +2510 | a: String, + | ^ +2511 | b: std::sync::Arc>, + | ^ + | + = note: `TestKP3` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + + ------- fields in this struct +2510 | a: String, + | ^ +2511 | b: std::sync::Arc>, + | ^ + | + = note: `TestKP3` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + +warning: associated functions `identity_typed` and `identity` are never used + --> src/lib.rs:2522:12 +warning: associated functions `identity_typed` and `identity` are never used + --> src/lib.rs:2522:12 + | +2514 | impl TestKP3 { + | ------------ associated functions in this implementation | +2514 | impl TestKP3 { + | ------------ associated functions in this implementation +... +2522 | fn identity_typed() -> Kp< + | ^^^^^^^^^^^^^^ +... +2541 | +... +2522 | fn identity_typed() -> Kp< + | ^^^^^^^^^^^^^^ +... +2541 | fn identity<'a>() -> KpType<'a, TestKP3, TestKP3> { + | ^^^^^^^^ + + fn identity<'a>() -> KpType<'a, TestKP3, TestKP3> { + | ^^^^^^^^ + +warning: field `age` is never read + --> warning: field `age` is never read + --> src/lib.rs:2942:13 + | +2940 | struct User { + | ----src/lib.rs:2942:13 + | +2940 | struct User { + | ---- field in this struct +2941 | name: String, +2942 | field in this struct +2941 | name: String, +2942 | age: i32, + | ^^^ + | + age: i32, + | ^^^ + | + = note: `User` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + + = note: `User` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + +warning: field `price` is never read + --> src/lib.rs:2948:13 + | +2946 | struct Product { + | -------warning: field `price` is never read + --> src/lib.rs:2948:13 + | +2946 | struct Product { + | ------- field in this struct +2947 | title: String, +2948 | price: f64, + field in this struct +2947 | title: String, +2948 | price: f64, + | ^^^^^ + | + = note: `Product` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + | ^^^^^ + | + = note: `Product` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis + + +warning: field `parent` is never read + --> warning: field `parent` is never read + --> src/lib.rs:4116:13 + | +4114 | struct Node { + |src/lib.rs:4116:13 + | +4114 | struct Node { + | ---- field in this struct +4115 | id: usize, +4116 ---- field in this struct +4115 | id: usize, +4116 | parent: Option>, + | ^^^^^^ + +warning: field `value` is never read + --> src/lib.rs:5020:13 + | +5019 | struct NodeWithParent { + | | parent: Option>, + | ^^^^^^ + +warning: field `value` is never read + --> src/lib.rs:5020:13 + | +5019 | struct NodeWithParent { + | -------------- field in this struct +5020 | value: i32, + | -------------- field in this struct +5020 | value: i32, + | ^^^^^ + +^^^^^ + +warning: variant `Response` is never constructed + warning: variant `Response` is never constructed + --> src/lib.rs:5292:13 + | +5290 | enum Message { + |--> src/lib.rs:5292:13 + | +5290 | enum Message { + | ------- variant in this enum +5291 | Request(LevelA), +5292 ------- variant in this enum +5291 | Request(LevelA), +5292 | Response(i32), + | ^^^^^^^^ + | + | Response(i32), + | ^^^^^^^^ + | + = note: `Message` has a derived impl for the trait `Clone`, but this is intentionally ignored during dead code analysis + += note: `Message` has a derived impl for the trait `Clone`, but this is intentionally ignored during dead code analysis + + Compiling rust-key-paths v2.6.0 (/Users/akashsoni/Documents/didl/rust-key-paths) + Compiling rust-key-paths v2.6.0 (/Users/akashsoni/Documents/didl/rust-key-paths) +warning: `rust-key-paths` (lib test) generated 39 warnings (4 duplicates) (run `cargo fix --lib -p rust-key-paths --tests` to apply 5 suggestions) +warning: `rust-key-paths` (lib test) generated 39 warnings (4 duplicates) (run `cargo fix --lib -p rust-key-paths --tests` to apply 5 suggestions) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s + Running unittests src/lib.rs (target/debug/deps/rust_key_paths-f3ffe169d4e3740b) + Running unittests src/lib.rs (target/debug/deps/rust_key_paths-f3ffe169d4e3740b) + Running tests/integration_async_lock.rs (target/debug/deps/integration_async_lock-141261c03c1ee9a1) + Running tests/integration_async_lock.rs (target/debug/deps/integration_async_lock-141261c03c1ee9a1) + Running tests/integration_nine_nesting.rs (target/debug/deps/integration_nine_nesting-0c55b333170b0009) + Running tests/integration_nine_nesting.rs (target/debug/deps/integration_nine_nesting-0c55b333170b0009) + Running tests/integration_pin_future.rs (target/debug/deps/integration_pin_future-b158f7685d420a9e) + Running tests/integration_pin_future.rs (target/debug/deps/integration_pin_future-b158f7685d420a9e) diff --git a/ADVANCED_TYPES_EXAMPLES.md b/ADVANCED_TYPES_EXAMPLES.md deleted file mode 100644 index f277f1a..0000000 --- a/ADVANCED_TYPES_EXAMPLES.md +++ /dev/null @@ -1,541 +0,0 @@ -# Advanced Keypath Examples: Pin, MaybeUninit, and Smart Pointers - -## Overview - -This document demonstrates how to use `Kp` keypaths with advanced Rust types including `Pin`, `MaybeUninit`, and various smart pointer patterns (`Arc`, `Rc`, `Weak`). - -## Pin Examples - -### What is Pin? - -`Pin

` ensures a value won't be moved in memory. This is crucial for: -- Self-referential structs -- Async/await (futures that reference themselves) -- FFI with position-dependent data structures - -### Example 1: Basic Pin with Self-Referential Struct - -```rust -use std::pin::Pin; - -#[derive(Debug)] -struct SelfReferential { - value: String, - ptr_to_value: *const String, // Points to value field -} - -impl SelfReferential { - fn new(s: String) -> Self { - let mut sr = Self { - value: s, - ptr_to_value: std::ptr::null(), - }; - sr.ptr_to_value = &sr.value as *const String; - sr - } -} - -// Create pinned value -let pinned: Pin> = Box::into_pin( - Box::new(SelfReferential::new("pinned_data".to_string())) -); - -// Keypath to access value field through Pin -let kp: KpType>, String> = Kp::new( - |p: &Pin>| { - Some(&p.as_ref().get_ref().value) - }, - |p: &mut Pin>| { - unsafe { - let sr = Pin::get_unchecked_mut(p.as_mut()); - Some(&mut sr.value) - } - }, -); - -let result = kp.get(&pinned); -assert_eq!(result, Some(&"pinned_data".to_string())); -``` - -**Key Points:** -- Use `Pin::as_ref().get_ref()` for immutable access -- Use `Pin::get_unchecked_mut()` for mutable access (requires `unsafe`) -- For `T: Unpin`, you can use `Pin::get_mut()` instead - -### Example 2: Pin> Pattern (Common in Async) - -```rust -use std::pin::Pin; -use std::sync::Arc; - -struct AsyncState { - status: String, - data: Vec, -} - -let pinned_arc: Pin> = Arc::pin(AsyncState { - status: "ready".to_string(), - data: vec![1, 2, 3, 4, 5], -}); - -// Keypath to status through Pin> -let status_kp: KpType>, String> = Kp::new( - |p: &Pin>| Some(&p.as_ref().get_ref().status), - |_: &mut Pin>| None::<&mut String>, // Arc is immutable -); - -let status = status_kp.get(&pinned_arc); -assert_eq!(status, Some(&"ready".to_string())); -``` - -**Key Points:** -- `Pin>` is common in async contexts -- Arc is immutable, so mutable keypaths typically return `None` -- Use `as_ref().get_ref()` to access the inner value - -## MaybeUninit Examples - -### What is MaybeUninit? - -`MaybeUninit` represents potentially uninitialized memory. Useful for: -- Optimizing initialization sequences -- Working with FFI/C code -- Building complex data structures incrementally -- Avoiding unnecessary zeroing - -### Example: Safe MaybeUninit with Keypaths - -```rust -use std::mem::MaybeUninit; - -struct Config { - name: MaybeUninit, - value: MaybeUninit, - initialized: bool, -} - -impl Config { - fn new_uninit() -> Self { - Self { - name: MaybeUninit::uninit(), - value: MaybeUninit::uninit(), - initialized: false, - } - } - - fn init(&mut self, name: String, value: i32) { - self.name.write(name); - self.value.write(value); - self.initialized = true; - } - - fn get_name(&self) -> Option<&String> { - if self.initialized { - unsafe { Some(self.name.assume_init_ref()) } - } else { - None - } - } -} - -// Create keypath that safely checks initialization -let name_kp: KpType = Kp::new( - |c: &Config| c.get_name(), - |c: &mut Config| { - if c.initialized { - unsafe { Some(c.name.assume_init_mut()) } - } else { - None - } - }, -); - -// Test with uninitialized config -let uninit_config = Config::new_uninit(); -assert_eq!(name_kp.get(&uninit_config), None); - -// Test with initialized config -let mut init_config = Config::new_uninit(); -init_config.init("test_config".to_string(), 42); -assert_eq!(name_kp.get(&init_config), Some(&"test_config".to_string())); -``` - -**Key Points:** -- Always check initialization state before using `assume_init_ref()`/`assume_init_mut()` -- Return `None` from keypaths when accessing uninitialized data -- Wrap unsafe operations in safe helper methods - -### Example: MaybeUninit Array Buffer - -```rust -use std::mem::MaybeUninit; - -struct Buffer { - data: [MaybeUninit; 10], - len: usize, // Track initialized elements -} - -impl Buffer { - fn new() -> Self { - Self { - data: unsafe { MaybeUninit::uninit().assume_init() }, - len: 0, - } - } - - fn push(&mut self, byte: u8) -> Result<(), &'static str> { - if self.len >= self.data.len() { - return Err("Buffer full"); - } - self.data[self.len].write(byte); - self.len += 1; - Ok(()) - } - - fn get(&self, idx: usize) -> Option<&u8> { - if idx < self.len { - unsafe { Some(self.data[idx].assume_init_ref()) } - } else { - None - } - } -} - -// Keypath to buffer length -let len_kp: KpType = Kp::new( - |b: &Buffer| Some(&b.len), - |b: &mut Buffer| Some(&mut b.len), -); - -let mut buffer = Buffer::new(); -buffer.push(1).unwrap(); -buffer.push(2).unwrap(); - -assert_eq!(len_kp.get(&buffer), Some(&2)); -assert_eq!(buffer.get(0), Some(&1)); -``` - -**Key Points:** -- Track initialization state separately (e.g., `len` field) -- Only access initialized elements -- Provide safe wrapper methods around unsafe operations - -## Smart Pointer Examples - -### Arc and Weak Pattern - -`Weak` provides non-owning references that don't prevent deallocation. Useful for: -- Breaking reference cycles -- Caching without preventing cleanup -- Observer patterns - -### Example: Simple Arc/Option Pattern - -```rust -use std::sync::Arc; - -struct NodeWithParent { - value: i32, - parent: Option>, // Strong reference -} - -struct Node { - value: i32, -} - -let parent = Arc::new(Node { value: 100 }); - -let child = NodeWithParent { - value: 42, - parent: Some(parent.clone()), -}; - -// Keypath to access parent value -let parent_value_kp: KpType = Kp::new( - |n: &NodeWithParent| n.parent.as_ref().map(|arc| &arc.value), - |_: &mut NodeWithParent| None::<&mut i32>, -); - -let parent_val = parent_value_kp.get(&child); -assert_eq!(parent_val, Some(&100)); -``` - -### Example: Rc Pattern (Single-Threaded) - -```rust -use std::rc::Rc; - -struct TreeNode { - value: String, - parent: Option>, -} - -let root = Rc::new(TreeNode { - value: "root".to_string(), - parent: None, -}); - -let child = TreeNode { - value: "child1".to_string(), - parent: Some(root.clone()), -}; - -// Keypath to access parent's value -let parent_name_kp: KpType = Kp::new( - |node: &TreeNode| node.parent.as_ref().map(|rc| &rc.value), - |_: &mut TreeNode| None::<&mut String>, -); - -assert_eq!(parent_name_kp.get(&child), Some(&"root".to_string())); -assert_eq!(parent_name_kp.get(&root), None); // Root has no parent -``` - -### Example: Nested Arc Structure - -```rust -use std::sync::Arc; - -struct Cache { - data: String, - backup: Option>, -} - -let primary = Arc::new(Cache { - data: "primary_data".to_string(), - backup: None, -}); - -let backup = Arc::new(Cache { - data: "backup_data".to_string(), - backup: Some(primary.clone()), -}); - -// Keypath to access backup's data -let backup_data_kp: KpType, String> = Kp::new( - |cache_arc: &Arc| { - cache_arc.backup.as_ref().map(|arc| &arc.data) - }, - |_: &mut Arc| None::<&mut String>, -); - -let data = backup_data_kp.get(&backup); -assert_eq!(data, Some(&"primary_data".to_string())); -``` - -## Combining Pin with Smart Pointers - -### Pin> Chaining Example - -```rust -use std::pin::Pin; -use std::sync::Arc; - -struct Outer { - inner: Arc, -} - -struct Inner { - value: String, -} - -let pinned_outer = Box::pin(Outer { - inner: Arc::new(Inner { - value: "nested_value".to_string(), - }), -}); - -// First keypath: Pin> -> Arc -let to_inner: KpType>, Arc> = Kp::new( - |p: &Pin>| Some(&p.as_ref().get_ref().inner), - |_: &mut Pin>| None::<&mut Arc>, -); - -// Second keypath: Arc -> String -let to_value: KpType, String> = Kp::new( - |a: &Arc| Some(&a.value), - |_: &mut Arc| None::<&mut String>, -); - -// Chain them together -let chained = to_inner.then(to_value); -let result = chained.get(&pinned_outer); -assert_eq!(result, Some(&"nested_value".to_string())); -``` - -## Safety Considerations - -### Pin Safety - -- **Immutable access**: Safe with `as_ref().get_ref()` -- **Mutable access**: Requires `unsafe` unless `T: Unpin` -- **Moving pinned data**: Never move data out of Pin -- **Purpose**: Guarantees memory location stability - -### MaybeUninit Safety - -- **Always check**: Track initialization state separately -- **Never assume**: Don't call `assume_init_*()` on uninitialized data -- **Undefined behavior**: Accessing uninitialized data is UB -- **Safe wrappers**: Provide methods that check before accessing - -### Weak Safety - -- **Upgrade can fail**: Always handle `None` from `upgrade()` -- **Temporary references**: Upgraded Arc lives only for the closure scope -- **No cycles**: Use Weak to break reference cycles -- **Thread safety**: `Weak` vs `rc::Weak` for threading needs - -## Best Practices - -### 1. Pin Usage -```rust -// ✅ Good: Safe immutable access -|p: &Pin>| Some(&p.as_ref().get_ref().field) - -// ⚠️ Requires unsafe: Mutable access to non-Unpin -|p: &mut Pin>| unsafe { Some(&mut Pin::get_unchecked_mut(p.as_mut()).field) } - -// ✅ Safe if T: Unpin -|p: &mut Pin>| Some(&mut Pin::get_mut(p).field) -``` - -### 2. MaybeUninit Usage -```rust -// ✅ Good: Check initialization state -|config: &Config| { - if config.initialized { - unsafe { Some(config.value.assume_init_ref()) } - } else { - None - } -} - -// ❌ Bad: Assuming initialization (UB if false!) -|config: &Config| unsafe { Some(config.value.assume_init_ref()) } -``` - -### 3. Weak References -```rust -// ✅ Good: Handle upgrade failure -|node: &Node| { - node.parent.as_ref() - .and_then(|weak| weak.upgrade()) - .map(|arc| &arc.value) -} - -// ❌ Bad: Unwrapping could panic -|node: &Node| { - Some(&node.parent.as_ref().unwrap().upgrade().unwrap().value) -} -``` - -## Performance Characteristics - -### Pin -- **Zero-cost abstraction**: No runtime overhead -- **Compile-time guarantee**: Memory location stability -- **Access cost**: Same as accessing T directly - -### MaybeUninit -- **No initialization**: Avoids default/zero initialization -- **Memory efficient**: Only initialize what you need -- **Access cost**: Negligible (just a pointer cast with safety check) - -### Weak -- **Upgrade cost**: Atomic operation to check reference count -- **Memory**: Weak pointer is same size as Arc/Rc -- **Failure handling**: Must handle `None` from upgrade - -## Common Patterns - -### Pattern 1: Pin + Arc for Async State - -```rust -let state: Pin> = Arc::pin(AsyncState::new()); -let status_kp = Kp::new( - |p: &Pin>| Some(&p.as_ref().get_ref().status), - |_| None, -); -``` - -**Use when:** Working with async tasks that need stable memory locations - -### Pattern 2: MaybeUninit for Lazy Initialization - -```rust -struct LazyData { - value: MaybeUninit, - initialized: bool, -} - -let kp = Kp::new( - |d: &LazyData| { - if d.initialized { - unsafe { Some(d.value.assume_init_ref()) } - } else { - None - } - }, - |_| None, -); -``` - -**Use when:** Expensive initialization that should be deferred - -### Pattern 3: Arc/Option for Optional Parents - -```rust -struct Node { - value: T, - parent: Option>, -} - -let parent_kp = Kp::new( - |n: &Node| n.parent.as_ref().map(|arc| &arc.value), - |_| None, -); -``` - -**Use when:** Tree structures with optional parent references - -## Tests - -All examples are validated with comprehensive tests: - -1. **`test_kp_with_pin`**: Basic Pin with self-referential struct -2. **`test_kp_with_pin_arc`**: Pin> pattern for async state -3. **`test_kp_with_maybe_uninit`**: Safe MaybeUninit with initialization tracking -4. **`test_kp_with_maybe_uninit_array`**: MaybeUninit array buffer -5. **`test_kp_with_weak`**: Arc with optional parent pattern -6. **`test_kp_with_rc_weak`**: Rc tree structure -7. **`test_kp_with_complex_weak_structure`**: Nested Arc references -8. **`test_kp_chain_with_pin_and_arc`**: Chaining through Pin and Arc - -**Total: 96 tests passing** (including all advanced type examples) - -## Limitations and Gotchas - -### Pin Limitations -- **Mutable access requires unsafe**: Unless `T: Unpin` -- **Can't move**: Once pinned, can't extract the value -- **Projection**: Pinning nested fields requires careful unsafe code - -### MaybeUninit Limitations -- **Must track state**: Need separate flag for initialization -- **All unsafe**: No safe way to access without assume_init -- **Lifetime complexity**: References to MaybeUninit data have complex lifetimes - -### Weak Limitations -- **Upgrade can fail**: Must always handle None case -- **Temporary lifetime**: Upgraded Arc/Rc lives only in closure scope -- **Can't return references**: Can't return `&T` from upgraded Weak in keypath closure - - **Workaround**: Use strong references (Arc/Rc) or copy/clone the value - -## Conclusion - -Keypaths work seamlessly with Rust's advanced type system features: -- **Pin**: Safe access to pinned data with clear unsafe boundaries -- **MaybeUninit**: Safe patterns for uninitialized memory with proper checking -- **Smart pointers**: Natural integration with Arc, Rc, and optional references - -All patterns maintain type safety while providing ergonomic access to complex data structures. diff --git a/ASYNC_LOCK_IMPLEMENTATION.md b/ASYNC_LOCK_IMPLEMENTATION.md deleted file mode 100644 index 12ca7a0..0000000 --- a/ASYNC_LOCK_IMPLEMENTATION.md +++ /dev/null @@ -1,290 +0,0 @@ -# Async Lock Keypath Implementation - -## Overview - -The `async_lock` module provides `AsyncLockKp` for type-safe, composable navigation through async locked data structures like `Arc>` and `Arc>`. - -## Key Components - -### 1. `AsyncLockAccess` Trait - -```rust -#[async_trait] -pub trait AsyncLockAccess: Send + Sync { - async fn lock_read(&self, lock: &Lock) -> Option; - async fn lock_write(&self, lock: &mut Lock) -> Option; -} -``` - -Unlike the synchronous `LockAccess` trait, this trait uses `async fn` methods, allowing for asynchronous lock acquisition. - -### 2. `AsyncLockKp` Struct - -```rust -pub struct AsyncLockKp -``` - -Structure: -- `prev`: Keypath from Root to Lock container (e.g., `Arc>`) -- `mid`: Async lock access handler (e.g., `TokioMutexAccess`) -- `next`: Keypath from Inner value to final Value - -### 3. Lock Implementations - -#### `TokioMutexAccess` - -Provides async access to `Arc>`: -- Uses `tokio::sync::Mutex::lock().await` for both read and write access -- Mutex provides exclusive access for all operations -- Good for simple async synchronization scenarios - -#### `TokioRwLockAccess` - -Provides async access to `Arc>`: -- Uses `tokio::sync::RwLock::read().await` for immutable access -- Uses `tokio::sync::RwLock::write().await` for mutable access -- Allows multiple concurrent readers -- Good for read-heavy async workloads - -## Comparison: Async vs Sync Locks - -| Feature | `LockKp` (Sync) | `AsyncLockKp` (Async) | -|---------|-----------------|----------------------| -| **Lock Acquisition** | Blocking | Asynchronous (`.await`) | -| **Trait Methods** | `fn lock_read/write` | `async fn lock_read/write` | -| **Supported Locks** | `Arc`, `Arc`, `Rc` | `Arc`, `Arc` | -| **Runtime** | Any (sync) | Requires async runtime (tokio) | -| **Use Case** | Thread-safe sync code | Async/await concurrent code | -| **Performance** | Lower overhead for sync code | Better for I/O-bound async workloads | - -## API Methods - -### `new(prev, mid, next)` - -Create a new `AsyncLockKp` from three components. - -### `async fn get_async(&self, root: Root) -> Option` - -Asynchronously get the value through the lock: -1. Navigate to the Lock using `prev` -2. Asynchronously lock and get the Inner value using `mid` -3. Navigate from Inner to final Value using `next` - -### `async fn get_mut_async(&self, root: MutRoot) -> Option` - -Asynchronously get mutable access to the value through the lock. - -### `async fn set_async(&self, root: Root, updater: F) -> Result<(), String>` - -Asynchronously set the value through the lock using an updater function. - -## Shallow Cloning Guarantee - -**IMPORTANT**: All cloning operations in `async_lock` are SHALLOW: - -1. **`AsyncLockKp` derives `Clone`**: Only clones function pointers and `PhantomData` -2. **`Lock: Clone` bound** (e.g., `Arc>`): - - For `Arc`: Only increments the atomic reference count - - The actual data `T` inside is **NEVER** cloned -3. **`L: Clone` bound** (e.g., `TokioMutexAccess`): - - Only clones `PhantomData` (zero-sized, zero-cost) - -This is proven by the `test_async_lock_kp_panic_on_clone_proof` test which uses a struct that panics if deeply cloned. - -## Example Usage - -### Basic Tokio Mutex - -```rust -use std::sync::Arc; -use tokio::sync::Mutex; -use rust_key_paths::{Kp, AsyncLockKp, AsyncTokioMutexAccess, KpType}; - -#[derive(Clone)] -struct Root { - data: Arc>, -} - -#[tokio::main] -async fn main() { - let root = Root { - data: Arc::new(Mutex::new("hello".to_string())), - }; - - // Create AsyncLockKp - let lock_kp = { - let prev: KpType>> = Kp::new( - |r: &Root| Some(&r.data), - |r: &mut Root| Some(&mut r.data), - ); - let next: KpType = Kp::new( - |s: &String| Some(s), - |s: &mut String| Some(s), - ); - AsyncLockKp::new(prev, AsyncTokioMutexAccess::new(), next) - }; - - // Async get - let value = lock_kp.get_async(&root).await; - assert_eq!(value, Some(&"hello".to_string())); -} -``` - -### Concurrent Reads with RwLock - -```rust -use std::sync::Arc; -use tokio::sync::RwLock; -use rust_key_paths::{Kp, AsyncLockKp, AsyncTokioRwLockAccess, KpType}; - -#[derive(Clone)] -struct Root { - data: Arc>, -} - -#[tokio::main] -async fn main() { - let root = Root { - data: Arc::new(RwLock::new(42)), - }; - - // Create AsyncLockKp - let make_lock_kp = || { - let prev: KpType>> = Kp::new( - |r: &Root| Some(&r.data), - |r: &mut Root| Some(&mut r.data), - ); - let next: KpType = Kp::new( - |n: &i32| Some(n), - |n: &mut i32| Some(n), - ); - AsyncLockKp::new(prev, AsyncTokioRwLockAccess::new(), next) - }; - - // Spawn multiple concurrent reads - let mut handles = vec![]; - for _ in 0..10 { - let root_clone = root.clone(); - let lock_kp = make_lock_kp(); - - let handle = tokio::spawn(async move { - lock_kp.get_async(&root_clone).await - }); - handles.push(handle); - } - - // All reads can happen concurrently with RwLock - for handle in handles { - let result = handle.await.unwrap(); - assert_eq!(result, Some(&42)); - } -} -``` - -## Tests - -The module includes 5 comprehensive tests: - -1. **`test_async_lock_kp_tokio_mutex_basic`**: Basic Tokio Mutex functionality -2. **`test_async_lock_kp_tokio_rwlock_basic`**: Basic Tokio RwLock functionality -3. **`test_async_lock_kp_concurrent_reads`**: Multiple concurrent async reads with RwLock -4. **`test_async_lock_kp_panic_on_clone_proof`**: Proves shallow cloning with panic-on-clone struct -5. **`test_async_lock_kp_structure`**: Verifies the three-field structure (prev, mid, next) - -## Feature Flag - -The async lock functionality requires the `tokio` feature flag: - -```toml -[dependencies] -rust-key-paths = { version = "1.27.0", features = ["tokio"] } -``` - -## Thread Safety - -- `AsyncLockAccess` requires `Send + Sync` bounds -- All implementations work across async task boundaries -- `Arc>` and `Arc>` are both `Send + Sync` when `T: Send + Sync` - -## Performance Characteristics - -### Shallow Cloning -- Cloning `AsyncLockKp`: O(1) - copies function pointers only -- Cloning `Arc>`: O(1) - atomic increment -- Cloning `TokioMutexAccess`: O(1) - zero-cost (PhantomData) - -### Lock Acquisition -- `tokio::sync::Mutex`: Uses OS-level parking for fairness -- `tokio::sync::RwLock`: Allows multiple concurrent readers -- Both are async-aware and won't block the executor - -## Unsafe Code - -The `AsyncLockAccess` implementations use `unsafe` to extend the lifetime of references obtained from async lock guards: - -```rust -async fn lock_read(&self, lock: &Arc>) -> Option<&'a T> { - let guard = lock.lock().await; - let ptr = &*guard as *const T; - unsafe { Some(&*ptr) } -} -``` - -**Safety Rationale**: -- The `Arc` ensures the data outlives the reference -- The lock guard is held during the critical section -- This pattern is necessary to work around lifetime constraints with async guards - -## Limitations - -### No Clone on AsyncLockKp with Closures - -Unlike function pointers, closures in Rust don't automatically implement `Clone`. Therefore, `AsyncLockKp` can only be cloned if all its function types are `Clone` (which is true for function pointers but not arbitrary closures). - -**Workaround**: Re-create the `AsyncLockKp` instead of cloning it: - -```rust -let make_lock_kp = || { - let prev = Kp::new(/* ... */); - let next = Kp::new(/* ... */); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) -}; - -// Use in multiple tasks -for _ in 0..10 { - let lock_kp = make_lock_kp(); - tokio::spawn(async move { - lock_kp.get_async(&root).await - }); -} -``` - -### No Chaining Methods (Yet) - -The `then()` and `compose()` methods present in `LockKp` are not yet implemented for `AsyncLockKp` due to complexity with async closures and the `impl Trait` return type with `Clone` bounds. - -**Current Status**: Basic functionality (new, get_async, get_mut_async, set_async) is fully working and tested. - -## Future Work - -- Implement `then()` for chaining with regular `Kp` -- Implement `compose()` for multi-level async lock chaining -- Support for other async runtimes (async-std, smol) -- Generic async lock trait that works across runtimes - -## Migration from Sync to Async - -If you have existing code using `LockKp` with `Arc>` or `Arc>` and want to migrate to async: - -1. Replace `Arc>` with `Arc>` -2. Replace `Arc>` with `Arc>` -3. Replace `LockKp` with `AsyncLockKp` -4. Replace `ArcMutexAccess` with `TokioMutexAccess` (re-exported as `AsyncTokioMutexAccess`) -5. Replace `ArcRwLockAccess` with `TokioRwLockAccess` (re-exported as `AsyncTokioRwLockAccess`) -6. Replace `.get()` with `.get_async().await` -7. Replace `.get_mut()` with `.get_mut_async().await` -8. Ensure your function is `async` and you're running on a Tokio runtime - -## Conclusion - -The `async_lock` module provides a type-safe, zero-cost abstraction for navigating through async locked data structures, maintaining the same shallow cloning guarantees and composability principles as the sync `lock` module, but adapted for the async/await paradigm. diff --git a/CASEPATHS_ENHANCEMENT.md b/CASEPATHS_ENHANCEMENT.md deleted file mode 100644 index 9c72f1a..0000000 --- a/CASEPATHS_ENHANCEMENT.md +++ /dev/null @@ -1,149 +0,0 @@ -# Casepaths Macro Enhancement - -## Overview - -The `Casepaths` derive macro has been updated to work with the new `rust-keypaths` API, providing automatic generation of enum variant keypaths. - -## Generated Methods - -For each enum variant, the macro generates: - -### Single-Field Variants -```rust -enum MyEnum { - Variant(InnerType), -} - -// Generated methods: -MyEnum::variant_fr() -> OptionalKeyPath -MyEnum::variant_fw() -> WritableOptionalKeyPath -``` - -### Multi-Field Tuple Variants -```rust -enum MyEnum { - Variant(T1, T2, T3), -} - -// Generated methods: -MyEnum::variant_fr() -> OptionalKeyPath -MyEnum::variant_fw() -> WritableOptionalKeyPath -``` - -### Named Field Variants -```rust -enum MyEnum { - Variant { field1: T1, field2: T2 }, -} - -// Generated methods: -MyEnum::variant_fr() -> OptionalKeyPath -MyEnum::variant_fw() -> WritableOptionalKeyPath -``` - -### Unit Variants -```rust -enum MyEnum { - Variant, -} - -// Generated methods: -MyEnum::variant_fr() -> OptionalKeyPath -``` - -## Attribute Support - -The macro supports the same attributes as `Keypaths`: - -- `#[Readable]` - Generate only readable methods (`_fr()`) -- `#[Writable]` - Generate only writable methods (`_fw()`) -- `#[All]` - Generate both readable and writable methods (default) - -### Example - -```rust -#[derive(Casepaths)] -#[Writable] // Generate only writable methods -enum MyEnum { - A(String), - B(Box), -} - -// Usage: -let path = MyEnum::b_fw() // Returns WritableOptionalKeyPath - .for_box() // Unwrap Box - .then(InnerStruct::field_fw()); -``` - -## Key Features - -1. **Type Safety**: Returns `OptionalKeyPath`/`WritableOptionalKeyPath` since variant extraction may fail -2. **Container Support**: Works seamlessly with `Box`, `Arc`, `Rc` via `.for_box()`, `.for_arc()`, `.for_rc()` -3. **Chaining**: Can be chained with `.then()` for nested access -4. **Attribute-Based Control**: Use `#[Readable]`, `#[Writable]`, or `#[All]` to control which methods are generated - -## Migration from Old API - -### Old API (key-paths-core) -```rust -#[derive(Casepaths)] -enum MyEnum { - Variant(InnerType), -} - -// Generated methods returned KeyPaths enum -let path = MyEnum::variant_r(); -``` - -### New API (rust-keypaths) -```rust -#[derive(Casepaths)] -#[Writable] -enum MyEnum { - Variant(InnerType), -} - -// Generated methods return specific types -let path = MyEnum::variant_fw(); // Returns WritableOptionalKeyPath -``` - -## Example: Deep Nesting with Box - -```rust -#[derive(Kp)] -#[Writable] -struct Outer { - inner: Option, -} - -#[derive(Casepaths)] -#[Writable] -enum MyEnum { - B(Box), -} - -#[derive(Kp)] -#[Writable] -struct InnerStruct { - field: Option, -} - -// Chain through Option -> Enum variant -> Box -> Option -let path = Outer::inner_fw() - .then(MyEnum::b_fw()) // Extract variant - .for_box() // Unwrap Box - .then(InnerStruct::field_fw()); - -// Use it -if let Some(value) = path.get_mut(&mut instance) { - *value = "new value".to_string(); -} -``` - -## Implementation Details - -- **Variant Extraction**: Uses pattern matching to safely extract variant values -- **Type Inference**: Automatically handles all variant field types -- **Error Handling**: Returns `None` if the enum is not the expected variant -- **Zero-Cost**: Compiles to direct pattern matching, no runtime overhead - diff --git a/CONTAINER_ADAPTERS.md b/CONTAINER_ADAPTERS.md deleted file mode 100644 index 7c67059..0000000 --- a/CONTAINER_ADAPTERS.md +++ /dev/null @@ -1,507 +0,0 @@ -# Container Adapters for rust-key-paths - -This document explains how to use keypaths with smart pointers and container types (`Arc`, `Box`, `Rc`) using the adapter methods. - -## Problem Statement - -When working with collections of wrapped types (e.g., `Vec>`), the standard keypaths don't work directly because they expect the unwrapped type: - -```rust -let products: Vec> = vec![Arc::new(Product { /* ... */ })]; -let price_path = Product::price_r(); // Returns KeyPaths - -// This doesn't work! price_path expects &Product but product is &Arc -for product in &products { - price_path.get(product); // Type mismatch! -} -``` - -## Solution: Adapter Methods - -The `key-paths-core` library provides three adapter methods that create new keypaths for wrapped types: - -### 1. `for_arc()` - Arc Adapter - -Adapts a `KeyPaths` to work with `Arc`: - -```rust -pub fn for_arc(self) -> KeyPaths, Value> -``` - -**Usage:** -```rust -let products: Vec> = /* ... */; -let price_path = Product::price_r().for_arc(); - -for product in &products { - if let Some(&price) = price_path.get(product) { - println!("Price: ${}", price); - } -} -``` - -**Supported KeyPath Types:** -- ✅ Readable -- ✅ FailableReadable -- ✅ ReadableEnum -- ❌ Writable (Arc is immutable) -- ❌ FailableWritable (Arc is immutable) - -### 2. `for_box()` - Box Adapter - -Adapts a `KeyPaths` to work with `Box`: - -```rust -pub fn for_box(self) -> KeyPaths, Value> -``` - -**Usage:** -```rust -let users: Vec> = /* ... */; -let name_path = User::name_r().for_box(); -let name_path_w = User::name_w().for_box(); // Writable works with Box! - -// Read access -for user in &users { - if let Some(name) = name_path.get(user) { - println!("Name: {}", name); - } -} - -// Write access -let mut users_mut = users; -if let Some(user) = users_mut.get_mut(0) { - if let Some(name) = name_path_w.get_mut(user) { - *name = "New Name".to_string(); - } -} -``` - -**Supported KeyPath Types:** -- ✅ Readable -- ✅ Writable -- ✅ FailableReadable -- ✅ FailableWritable -- ✅ ReadableEnum -- ✅ WritableEnum - -### 3. `for_rc()` - Rc Adapter - -Adapts a `KeyPaths` to work with `Rc`: - -```rust -pub fn for_rc(self) -> KeyPaths, Value> -``` - -**Usage:** -```rust -let products: Vec> = /* ... */; -let category_path = Product::category_r().for_rc(); - -for product in &products { - if let Some(category) = category_path.get(product) { - println!("Category: {}", category); - } -} -``` - -**Supported KeyPath Types:** -- ✅ Readable -- ✅ FailableReadable -- ✅ ReadableEnum -- ❌ Writable (Rc is immutable) -- ❌ FailableWritable (Rc is immutable) - -## Common Patterns - -### Pattern 1: Filtering Wrapped Collections - -```rust -let products: Vec> = /* ... */; -let price_path = Product::price_r().for_arc(); -let in_stock_path = Product::in_stock_r().for_arc(); - -let affordable: Vec<&Arc> = products - .iter() - .filter(|p| { - price_path.get(p).map_or(false, |&price| price < 100.0) - && in_stock_path.get(p).map_or(false, |&stock| stock) - }) - .collect(); -``` - -### Pattern 2: Grouping by Field - -```rust -use std::collections::HashMap; - -let products: Vec> = /* ... */; -let category_path = Product::category_r().for_arc(); - -let mut by_category: HashMap>> = HashMap::new(); - -for product in products { - if let Some(category) = category_path.get(&product) { - by_category - .entry(category.clone()) - .or_insert_with(Vec::new) - .push(product); - } -} -``` - -### Pattern 3: Shared State with Arc - -```rust -use std::sync::Arc; - -// Common pattern: Vec> for shared ownership -let shared_data: Vec> = vec![ - Arc::new(Product { /* ... */ }), -]; - -// Clone Arcs (cheap - just increments reference count) -let thread1_data = shared_data.clone(); -let thread2_data = shared_data.clone(); - -// Both threads can query using adapted keypaths -let price_path = Product::price_r().for_arc(); - -// Thread 1 -let expensive = thread1_data - .iter() - .filter(|p| price_path.get(p).map_or(false, |&p| p > 100.0)) - .collect::>(); - -// Thread 2 sees the same data -let total: f64 = thread2_data - .iter() - .filter_map(|p| price_path.get(p).copied()) - .sum(); -``` - -### Pattern 4: Mutable Access with Box - -```rust -let mut users: Vec> = /* ... */; -let age_path = User::age_w().for_box(); - -// Increment everyone's age -for user in &mut users { - if let Some(age) = age_path.get_mut(user) { - *age += 1; - } -} -``` - -### Pattern 5: HashMap Values - -```rust -use std::collections::HashMap; -use std::sync::Arc; - -let map: HashMap> = /* ... */; - -// Collect Arc references -let products: Vec> = map.values().cloned().collect(); - -// Query with adapted keypath -let price_path = Product::price_r().for_arc(); -let results: Vec<&Arc> = products - .iter() - .filter(|p| price_path.get(p).map_or(false, |&price| price < 200.0)) - .collect(); -``` - -## Adapter Chaining - -Adapters can be chained for nested containers (though this is uncommon): - -```rust -// Vec>> (unusual but possible) -let nested: Vec>> = /* ... */; - -// Chain adapters: Product -> Box -> Arc> -let price_path = Product::price_r() - .for_box() - .for_arc(); - -for item in &nested { - if let Some(&price) = price_path.get(item) { - println!("Price: ${}", price); - } -} -``` - -## Composition with Adapters - -Adapted keypaths work with composition: - -```rust -#[derive(Kp)] -struct Order { - product: Product, - quantity: u32, -} - -let orders: Vec> = /* ... */; - -// Compose adapted keypaths -let product_price_path = Order::product_r() - .then(Product::price_r()) - .for_arc(); - -for order in &orders { - if let Some(&price) = product_price_path.get(order) { - println!("Product price: ${}", price); - } -} -``` - -## Performance Characteristics - -### Zero-Cost Abstraction - -The adapter methods create new keypaths that dereference the wrapper at access time. The Rust compiler optimizes this to direct field access: - -```rust -// This: -let price = price_path_arc.get(&product); - -// Compiles to the same code as: -let price = &(**product).price; -``` - -### Memory Overhead - -- **Arc/Rc Cloning**: O(1) - Just increments reference count -- **Box Moving**: O(1) - Moves pointer, not data -- **Adapter Creation**: O(1) - Creates new keypath wrapper - -### Reference Counting - -With `Arc` and `Rc`, be aware of reference counting implications: - -```rust -let products: Vec> = /* ... */; - -// Cloning products doesn't clone Product data -let clone = products.clone(); // Fast: just increments refcounts - -// But keeping references prevents deallocation -let forever = products[0].clone(); // Product won't be freed while `forever` exists -``` - -## Common Pitfalls & Solutions - -### Pitfall 1: Trying to Mutate Through Arc/Rc - -**Problem:** -```rust -let data: Vec> = /* ... */; -let price_path = Product::price_w().for_arc(); // Panics! -``` - -**Solution:** Use `Box` for mutable access, or use interior mutability (`Mutex`, `RwLock`): -```rust -// Option 1: Use Box instead -let data: Vec> = /* ... */; -let price_path = Product::price_w().for_box(); // Works! - -// Option 2: Use Arc with interior mutability -use parking_lot::RwLock; -let data: Vec>> = /* ... */; -// Access through RwLock -``` - -### Pitfall 2: Forgetting to Adapt - -**Problem:** -```rust -let products: Vec> = /* ... */; -let price_path = Product::price_r(); // Not adapted! - -for product in &products { - price_path.get(product); // Type error! -} -``` - -**Solution:** Always use the adapter: -```rust -let price_path = Product::price_r().for_arc(); // ✓ Adapted -``` - -### Pitfall 3: Adapter Order Matters - -**Problem:** -```rust -// Wrong order: composing THEN adapting -let path = Order::product_r() - .for_arc() // Too early! - .then(Product::price_r()); // Type mismatch -``` - -**Solution:** Compose first, adapt last: -```rust -// Correct: compose THEN adapt -let path = Order::product_r() - .then(Product::price_r()) - .for_arc(); // ✓ Correct -``` - -## Integration with Query Builders - -If you're using a query builder library, adapted keypaths work seamlessly: - -```rust -let products: Vec> = /* ... */; - -// Create adapted keypaths once -let price_path = Product::price_r().for_arc(); -let category_path = Product::category_r().for_arc(); - -// Use in queries -let results = products - .iter() - .filter(|p| { - price_path.get(p).map_or(false, |&price| price > 100.0) - && category_path.get(p).map_or(false, |cat| cat == "Electronics") - }) - .collect(); -``` - -## Best Practices - -1. **Create Adapted Keypaths Once** - ```rust - // Good: Create once, use many times - let price_path = Product::price_r().for_arc(); - for product in &products { - price_path.get(product); - } - - // Bad: Creating adapter in loop - for product in &products { - Product::price_r().for_arc().get(product); // Wasteful - } - ``` - -2. **Choose the Right Container** - - `Arc`: Shared ownership, thread-safe, immutable - - `Rc`: Shared ownership, single-threaded, immutable - - `Box`: Unique ownership, mutable - -3. **Prefer Arc for Shared Data** - ```rust - // Good: Arc for sharing between threads/owners - let data: Vec> = /* ... */; - let clone = data.clone(); // Cheap - - // Bad: Box requires actual cloning - let data: Vec> = /* ... */; - let clone = data.clone(); // Expensive if Product is large - ``` - -4. **Use get_ref() for Reference Collections** - ```rust - // If you have Vec<&Arc>, combine with get_ref: - let refs: Vec<&Arc> = /* ... */; - let price_path = Product::price_r().for_arc(); - - for product_ref in &refs { - price_path.get_ref(product_ref); - } - ``` - -## Examples - -See [`examples/container_adapters.rs`](examples/container_adapters.rs) for comprehensive examples including: - -- Vec> with filtering -- Vec> with mutable access -- Vec> with grouping -- Shared state patterns -- Complex filtering scenarios -- Mixed container types - -Run with: -```bash -cargo run --example container_adapters -``` - -## API Reference - -### for_arc() - -```rust -pub fn for_arc(self) -> KeyPaths, Value> -where - Root: 'static, - Value: 'static -``` - -Adapts this keypath to work with `Arc`. - -**Panics:** If called on a Writable or FailableWritable keypath (Arc is immutable). - -### for_box() - -```rust -pub fn for_box(self) -> KeyPaths, Value> -where - Root: 'static, - Value: 'static -``` - -Adapts this keypath to work with `Box`. Supports both readable and writable access. - -### for_rc() - -```rust -pub fn for_rc(self) -> KeyPaths, Value> -where - Root: 'static, - Value: 'static -``` - -Adapts this keypath to work with `Rc`. - -**Panics:** If called on a Writable or FailableWritable keypath (Rc is immutable). - -## Future Enhancements - -Potential future additions: - -1. **Generic Deref Adapter**: Single `.for_deref()` method that works with any `Deref` type -2. **Nested Container Support**: Automatic multi-level dereferencing -3. **Interior Mutability Support**: `.for_arc_mutex()`, `.for_arc_rwlock()` -4. **Macro Helpers**: `arc_path!(Product::price_r)` shorthand - -## Troubleshooting - -### Error: "Cannot create writable keypath for Arc" - -**Problem:** Trying to use `.for_arc()` with a writable keypath - -**Solution:** Arc is immutable. Use `Box` or `Rc>` for mutation - -### Error: "expected Arc, found T" - -**Problem:** Forgot to adapt the keypath - -**Solution:** Add `.for_arc()` after creating the keypath - -### Error: "method `for_arc` not found" - -**Problem:** Using an older version of key-paths-core - -**Solution:** Update to key-paths-core >= 1.0.2 - -## Contributing - -Found a bug or have a suggestion? Please file an issue or submit a PR! - -## License - -Same as rust-key-paths: MPL-2.0 - diff --git a/Cargo.toml b/Cargo.toml index 67bf617..5812913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-key-paths" -version = "2.0.7" +version = "2.9.0" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" @@ -41,7 +41,7 @@ tokio = ["dep:tokio"] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" parking_lot = "0.12" -tagged-core = "0.8.0" +tagged-core = "1.0.1" chrono = { version = "0.4", features = ["serde"] } uuid = { version = "1.0", features = ["v4", "serde"] } criterion = { version = "0.5", features = ["html_reports"] } diff --git a/EDGE_CASES_REVIEW.md b/EDGE_CASES_REVIEW.md deleted file mode 100644 index 344091a..0000000 --- a/EDGE_CASES_REVIEW.md +++ /dev/null @@ -1,115 +0,0 @@ -# Edge Cases Review for Rust Key-Paths Library - -## Current Status (Updated) - -### ✅ Working Cases -- **Basic types**: `String`, `i32`, `bool` - all working correctly -- **Basic containers**: `Option`, `Vec`, `Box`, `Rc`, `Arc` - all working correctly -- **Collections**: `HashSet`, `BTreeSet`, `VecDeque`, `LinkedList`, `BinaryHeap` - all working (after fixes) -- **Maps**: `HashMap`, `BTreeMap` - all working (after fixes) - -### ✅ Issues Fixed -- **BTreeMap Generic Constraints**: Fixed by removing problematic key-based access methods -- **BinaryHeap Type Issues**: Fixed by removing failable methods that had `str` vs `String` issues -- **Type Variable Usage**: Fixed for all basic container types - -### ❌ Remaining Issues - -#### 1. Nested Container Issues -- **Problem**: `Box>` generates wrong return types -- **Cause**: Macro not correctly handling nested container combinations -- **Impact**: All nested combinations fail -- **Status**: Partially debugged - detection works but generation has type issues - -### 🔧 Container Types Support Status - -| Container Type | Status | Issues | -|----------------|--------|---------| -| `Option` | ✅ Working | None | -| `Vec` | ✅ Working | None | -| `Box` | ✅ Working | None | -| `Rc` | ✅ Working | None | -| `Arc` | ✅ Working | None | -| `HashSet` | ✅ Working | None (fixed) | -| `BTreeSet` | ✅ Working | None (fixed) | -| `VecDeque` | ✅ Working | None (fixed) | -| `LinkedList` | ✅ Working | None (fixed) | -| `BinaryHeap` | ✅ Working | Limited failable methods (fixed) | -| `HashMap` | ✅ Working | None (fixed) | -| `BTreeMap` | ✅ Working | Limited key-based methods (fixed) | - -### 🔧 Nested Combinations Status - -| Combination | Status | Issues | -|-------------|--------|---------| -| `Option>` | ❌ Failing | Type mismatch | -| `Box>` | ❌ Failing | Type mismatch | -| `Option>` | ❌ Failing | Type mismatch | -| `Vec>` | ❌ Failing | Type mismatch | -| `Rc>` | ❌ Failing | Type mismatch | -| `Arc>` | ❌ Failing | Type mismatch | - -## Root Cause Analysis - -The main issues stem from: - -1. **Incorrect Type Variable Usage**: The macro is using wrong type variables in return types -2. **Generic Constraint Issues**: BTreeMap key access requires proper generic constraints -3. **Nested Type Handling**: The recursive type extraction is not working correctly for nested combinations - -## Recommended Fixes - -### 1. Fix Type Variable Usage -- Ensure `#ty` refers to the full field type (e.g., `Box>`) -- Ensure `#inner_ty` refers to the extracted inner type (e.g., `String`) -- Fix return types in all macro cases - -### 2. Fix BTreeMap Constraints -- Add proper generic constraints for BTreeMap key access -- Use correct key types in generated methods - -### 3. Fix Nested Combinations -- Debug the `extract_wrapper_inner_type` function -- Ensure nested combinations are correctly detected and handled - -### 4. Add Comprehensive Tests -- Create tests for all container types -- Create tests for all nested combinations -- Add error case tests - -## Test Coverage Needed - -### Basic Container Tests -- [ ] `HashSet` - failable access to elements -- [ ] `BTreeSet` - failable access to elements -- [ ] `VecDeque` - indexed access -- [ ] `LinkedList` - indexed access -- [ ] `BinaryHeap` - peek access -- [ ] `HashMap` - key-based access -- [ ] `BTreeMap` - key-based access - -### Nested Combination Tests -- [ ] `Option>` - all method types -- [ ] `Box>` - all method types -- [ ] `Option>` - all method types -- [ ] `Vec>` - all method types -- [ ] `Rc>` - all method types -- [ ] `Arc>` - all method types - -### Edge Case Tests -- [ ] Empty containers -- [ ] Single element containers -- [ ] Large containers -- [ ] Complex nested structures -- [ ] Error conditions -- [ ] Composition between different keypath types - -## Conclusion - -The library has good foundation support for basic containers but needs fixes for: -1. Type variable usage in macro generation -2. Generic constraints for map types -3. Nested container combinations -4. Comprehensive test coverage - -Once these issues are resolved, the library will provide comprehensive support for all Rust standard library container types and their combinations. diff --git a/EXAMPLES_FIXES_FINAL.md b/EXAMPLES_FIXES_FINAL.md deleted file mode 100644 index fa4dac7..0000000 --- a/EXAMPLES_FIXES_FINAL.md +++ /dev/null @@ -1,113 +0,0 @@ -# Examples Fixes - Final Status - -## ✅ Completed Enhancements to rust-keypaths - -### 1. Added `to_optional()` Methods -- **KeyPath::to_optional()** - Converts `KeyPath` to `OptionalKeyPath` for chaining -- **WritableKeyPath::to_optional()** - Converts `WritableKeyPath` to `WritableOptionalKeyPath` for chaining - -### 2. Added Container Adapter Methods -- **OptionalKeyPath::with_option()** - Execute closure with value inside `Option` -- **OptionalKeyPath::with_mutex()** - Execute closure with value inside `Mutex` -- **OptionalKeyPath::with_rwlock()** - Execute closure with value inside `RwLock` -- **OptionalKeyPath::with_arc_rwlock()** - Execute closure with value inside `Arc>` -- **OptionalKeyPath::with_arc_mutex()** - Execute closure with value inside `Arc>` -- **KeyPath::with_arc_rwlock_direct()** - Direct support for `Arc>` -- **KeyPath::with_arc_mutex_direct()** - Direct support for `Arc>` - -### 3. Added `get()` to WritableKeyPath -- **WritableKeyPath::get()** - Returns `&Value` (requires `&mut Root`) -- Note: For optional fields, use `WritableOptionalKeyPath::get_mut()` which returns `Option<&mut Value>` - -## 📊 Current Status - -- ✅ **rust-keypaths library**: Compiles successfully -- ✅ **keypaths-proc macro**: Compiles successfully -- ⚠️ **Examples**: ~41 errors remaining (down from 131) - -## 🔧 Remaining Issues - -### 1. Type Mismatches (29 errors) -- **Issue**: Examples expect `WritableKeyPath::get()` to return `Option<&mut Value>` -- **Reality**: `WritableKeyPath::get()` returns `&Value` (non-optional) -- **Solution**: Examples should use: - - `WritableOptionalKeyPath::get_mut()` for optional fields - - Or convert using `.to_optional()` first - -### 2. Missing Methods (9 errors) -- `with_arc_rwlock_direct` / `with_arc_mutex_direct` - ✅ **FIXED** (just added) -- `extract_from_slice` - May need to be added if used in examples - -### 3. Clone Issues (2 errors) -- Some `KeyPath` instances don't satisfy `Clone` bounds -- This happens when the closure type doesn't implement `Clone` -- **Solution**: Use `.clone()` only when the keypath is from derive macros (which generate Clone-able closures) - -### 4. Other Issues (1 error) -- `Option<&mut T>` cannot be dereferenced - Need to use `if let Some(x) = ...` pattern -- Missing `main` function in one example - -## 🎯 Next Steps - -1. **Fix WritableKeyPath usage**: Update examples to use `WritableOptionalKeyPath` for optional fields -2. **Add missing methods**: Add `extract_from_slice` if needed -3. **Fix type mismatches**: Update examples to match the actual API -4. **Test all examples**: Run each example to verify it works correctly - -## 📝 API Summary - -### KeyPath API -```rust -// Direct access (non-optional) -let kp = KeyPath::new(|r: &Root| &r.field); -let value = kp.get(&root); // Returns &Value - -// Convert to optional for chaining -let opt_kp = kp.to_optional(); // Returns OptionalKeyPath -let value = opt_kp.get(&root); // Returns Option<&Value> - -// Container adapters -kp.with_option(&opt, |v| ...); -kp.with_mutex(&mutex, |v| ...); -kp.with_rwlock(&rwlock, |v| ...); -kp.with_arc_rwlock_direct(&arc_rwlock, |v| ...); -``` - -### WritableKeyPath API -```rust -// Direct mutable access (non-optional) -let wk = WritableKeyPath::new(|r: &mut Root| &mut r.field); -let value = wk.get_mut(&mut root); // Returns &mut Value -let value_ref = wk.get(&mut root); // Returns &Value - -// Convert to optional for chaining -let opt_wk = wk.to_optional(); // Returns WritableOptionalKeyPath -``` - -### OptionalKeyPath API -```rust -// Failable access -let okp = OptionalKeyPath::new(|r: &Root| r.field.as_ref()); -let value = okp.get(&root); // Returns Option<&Value> - -// Chaining -let chained = okp.then(other_okp); - -// Container adapters -okp.with_option(&opt, |v| ...); -okp.with_mutex(&mutex, |v| ...); -okp.with_rwlock(&rwlock, |v| ...); -``` - -## 🚀 Migration Notes - -When migrating from `key-paths-core` to `rust-keypaths`: - -1. **KeyPaths enum** → Use specific types (`KeyPath`, `OptionalKeyPath`, etc.) -2. **KeyPaths::readable()** → `KeyPath::new()` -3. **KeyPaths::failable_readable()** → `OptionalKeyPath::new()` -4. **.compose()** → `.then()` (only on `OptionalKeyPath`) -5. **WithContainer trait** → Use `with_*` methods directly on keypaths -6. **KeyPath.get()** → Returns `&Value` (not `Option`) -7. **WritableKeyPath.get_mut()** → Returns `&mut Value` (not `Option`) - diff --git a/EXAMPLES_FIXES_SUMMARY.md b/EXAMPLES_FIXES_SUMMARY.md deleted file mode 100644 index 1bcfa80..0000000 --- a/EXAMPLES_FIXES_SUMMARY.md +++ /dev/null @@ -1,83 +0,0 @@ -# Examples Fixes Summary - -## Completed Fixes - -### 1. Added `to_optional()` Method to `KeyPath` -- **Location**: `rust-keypaths/src/lib.rs` -- **Purpose**: Allows `KeyPath` to be converted to `OptionalKeyPath` for chaining with `then()` -- **Usage**: `keypath.to_optional().then(other_keypath)` - -### 2. Updated All Example Imports -- Changed `key_paths_derive` → `keypaths_proc` -- Changed `key_paths_core::KeyPaths` → `rust_keypaths::{KeyPath, OptionalKeyPath, ...}` - -### 3. Fixed API Method Calls -- `KeyPaths::readable()` → `KeyPath::new()` -- `KeyPaths::failable_readable()` → `OptionalKeyPath::new()` -- `KeyPaths::writable()` → `WritableKeyPath::new()` -- `KeyPaths::failable_writable()` → `WritableOptionalKeyPath::new()` -- `.compose()` → `.then()` - -### 4. Fixed `get()` and `get_mut()` Patterns -- **KeyPath::get()**: Returns `&Value` directly (not `Option`) - - Fixed: Removed `if let Some()` patterns for `KeyPath` -- **WritableKeyPath::get_mut()**: Returns `&mut Value` directly (not `Option`) - - Fixed: Removed `if let Some()` patterns for `WritableKeyPath` - -### 5. Fixed Chaining Issues -- **Problem**: `KeyPath` doesn't have `then()` method -- **Solution**: Use `.to_optional()` to convert `KeyPath` to `OptionalKeyPath` before chaining -- **Pattern**: `Struct::field_r().to_optional().then(...)` - -## Working Examples - -✅ **basics.rs** - Compiles and runs successfully -- Demonstrates basic `KeyPath` and `WritableKeyPath` usage -- Shows direct `get()` and `get_mut()` access (no Option wrapping) - -## Remaining Issues - -### Common Error Patterns - -1. **`then()` on `KeyPath`** (15 errors) - - **Fix**: Add `.to_optional()` before `.then()` - - **Pattern**: `keypath.to_optional().then(...)` - -2. **Type Mismatches** (11 errors) - - **Cause**: `KeyPath::get()` returns `&Value`, not `Option<&Value>` - - **Fix**: Remove `if let Some()` for `KeyPath`, keep for `OptionalKeyPath` - -3. **Missing Methods** (4 errors) - - Some derive macro methods may not be generated correctly - - Need to verify `keypaths-proc` generates all expected methods - -4. **`WithContainer` Trait** (1 error) - - **Issue**: `rust-keypaths` doesn't have `WithContainer` trait - - **Fix**: Use `containers` module functions directly - -## Next Steps - -1. **Fix Remaining `then()` Errors**: Add `.to_optional()` where needed -2. **Fix Type Mismatches**: Update `get()` usage patterns -3. **Verify Derive Macro**: Ensure all methods are generated correctly -4. **Update Complex Examples**: Fix examples with deep nesting and complex patterns - -## Testing Status - -- ✅ `rust-keypaths` library compiles -- ✅ `keypaths-proc` proc macro compiles -- ✅ `basics.rs` example works -- ⚠️ ~126 errors remaining across other examples -- ⚠️ Most errors are fixable with pattern replacements - -## Key API Differences - -| Old API (`key-paths-core`) | New API (`rust-keypaths`) | -|---------------------------|---------------------------| -| `KeyPaths::readable()` | `KeyPath::new()` | -| `KeyPaths::failable_readable()` | `OptionalKeyPath::new()` | -| `keypath.get()` → `Option<&Value>` | `keypath.get()` → `&Value` (KeyPath) | -| `keypath.get()` → `Option<&Value>` | `keypath.get()` → `Option<&Value>` (OptionalKeyPath) | -| `keypath.compose(other)` | `keypath.then(other)` (OptionalKeyPath only) | -| `KeyPath` can chain | `KeyPath` needs `.to_optional()` to chain | - diff --git a/EXAMPLES_MIGRATION_STATUS.md b/EXAMPLES_MIGRATION_STATUS.md deleted file mode 100644 index 0579bac..0000000 --- a/EXAMPLES_MIGRATION_STATUS.md +++ /dev/null @@ -1,137 +0,0 @@ -# Examples Migration Status - -## Overview - -The examples in the `examples/` directory were originally written for the `key-paths-core` (dynamic dispatch) API and need updates to work with the new `rust-keypaths` (static dispatch) API. - -## Status - -- ✅ **Imports Updated**: All 80+ examples have been updated to use `keypaths-proc` and `rust-keypaths` instead of `key-paths-derive` and `key-paths-core` -- ⚠️ **API Updates Needed**: Many examples still need API changes to work with the new type-based system - -## Common Issues - -### 1. `KeyPath` vs `OptionalKeyPath` for Chaining - -**Problem**: `KeyPath` doesn't have a `then()` method - only `OptionalKeyPath` does. - -**Solution**: Use `_fr()` methods (failable readable) instead of `_r()` methods when you need to chain: - -```rust -// ❌ Wrong - KeyPath can't chain -let path = Struct::field_r().then(Other::value_r()); - -// ✅ Correct - Use OptionalKeyPath for chaining -let path = Struct::field_fr().then(Other::value_fr()); -``` - -### 2. `get()` Return Type - -**Problem**: `KeyPath::get()` returns `&Value` directly, not `Option<&Value>`. - -**Solution**: Remove `if let Some()` patterns for `KeyPath`: - -```rust -// ❌ Wrong -if let Some(value) = keypath.get(&instance) { - // ... -} - -// ✅ Correct -let value = keypath.get(&instance); -// use value directly -``` - -### 3. `get_mut()` Method - -**Problem**: `KeyPath` doesn't have `get_mut()` - only `WritableKeyPath` does. - -**Solution**: Use `_w()` methods for writable access: - -```rust -// ❌ Wrong -let mut_ref = keypath.get_mut(&mut instance); - -// ✅ Correct -let writable_kp = Struct::field_w(); -let mut_ref = writable_kp.get_mut(&mut instance); -``` - -### 4. Return Type Annotations - -**Problem**: The new API uses `impl Trait` in return types which can't be stored in struct fields. - -**Solution**: Use type inference or store the keypath in a variable without explicit type: - -```rust -// ❌ Wrong - can't use impl Trait in struct fields -struct MyStruct { - keypath: KeyPath &Value>, -} - -// ✅ Correct - use type inference -let keypath = KeyPath::new(|r: &Root| &r.field); -// or use a type alias if needed -``` - -### 5. Missing `WithContainer` Trait - -**Problem**: `rust-keypaths` doesn't have a `WithContainer` trait. - -**Solution**: Use the `containers` module functions directly: - -```rust -// ❌ Wrong -use rust_keypaths::WithContainer; - -// ✅ Correct -use rust_keypaths::containers; -let vec_kp = containers::for_vec_index::(0); -``` - -## Migration Checklist - -For each example file: - -- [ ] Update imports: `key_paths_derive` → `keypaths_proc` -- [ ] Update imports: `key_paths_core::KeyPaths` → `rust_keypaths::{KeyPath, OptionalKeyPath, ...}` -- [ ] Replace `KeyPaths::readable()` → `KeyPath::new()` -- [ ] Replace `KeyPaths::failable_readable()` → `OptionalKeyPath::new()` -- [ ] Replace `KeyPaths::writable()` → `WritableKeyPath::new()` -- [ ] Replace `KeyPaths::failable_writable()` → `WritableOptionalKeyPath::new()` -- [ ] Replace `.compose()` → `.then()` -- [ ] Fix `get()` calls: Remove `if let Some()` for `KeyPath`, keep for `OptionalKeyPath` -- [ ] Fix chaining: Use `_fr()` methods instead of `_r()` when chaining -- [ ] Fix `get_mut()`: Use `_w()` methods to get `WritableKeyPath` -- [ ] Remove `WithContainer` usage if present -- [ ] Update return type annotations to avoid `impl Trait` in struct fields - -## Examples That Work - -These examples should work with minimal changes: -- Simple examples using only `KeyPath::new()` and `get()` -- Examples using only `OptionalKeyPath::new()` and `get()` -- Examples that don't chain keypaths - -## Examples That Need More Work - -These examples need significant refactoring: -- Examples using `KeyPaths` enum in struct fields -- Examples using `WithContainer` trait -- Examples with complex chaining that use `_r()` methods -- Examples using owned keypaths (not supported in new API) - -## Testing Strategy - -1. Start with simple examples (e.g., `basics.rs`, `keypath_simple.rs`) -2. Fix common patterns in those examples -3. Apply fixes to similar examples -4. Handle complex examples individually - -## Next Steps - -1. Create a test script to identify which examples compile -2. Fix examples one category at a time (simple → complex) -3. Update documentation to reflect the new API patterns -4. Create new examples showcasing the new API's strengths - diff --git a/EXAMPLES_SUMMARY.md b/EXAMPLES_SUMMARY.md deleted file mode 100644 index f291c9e..0000000 --- a/EXAMPLES_SUMMARY.md +++ /dev/null @@ -1,452 +0,0 @@ -# Examples Summary - rust-key-paths - -This document provides an overview of all the examples in this repository, organized by functionality and use case. - -## 📚 Example Categories - -### 1. **Form Processing & Validation** - -#### `examples/user_form.rs` -**Purpose:** Basic form validation and processing using keypaths - -**Features:** -- Type-safe form field definitions -- Custom validators per field -- Generic form processing -- Nested field updates (e.g., `settings.theme`) - -**Run:** `cargo run --example user_form` - -**Key Concepts:** -- FormField struct with keypaths -- Validator functions -- Generic `process_form()` function - ---- - -#### `examples/form_binding.rs` -**Purpose:** Advanced UI field binding without hardcoded access patterns - -**Features:** -- Two-way data binding (read & write) -- Multiple field types (String, bool, u32) -- Field-level validation -- Update fields by name -- Display current form state - -**Run:** `cargo run --example form_binding` - -**Key Concepts:** -- FormBinding system -- Type-specific field collections -- Validation before writes -- Field lookup by name - ---- - -### 2. **State Management & Synchronization** - -#### `examples/change_tracker.rs` -**Purpose:** Track and synchronize changes between states - -**Features:** -- Detect changes between old and new state -- Serialize changes to JSON -- Apply changes from remote sources -- Bidirectional synchronization - -**Run:** `cargo run --example change_tracker` - -**Key Concepts:** -- ChangeTracker with read/write paths -- FieldChange serialization -- Deserialization and application -- State verification - ---- - -#### `examples/undo_redo.rs` -**Purpose:** Implement undo/redo for deeply nested data structures - -**Features:** -- Command pattern for changes -- Full undo/redo stack -- Change history display -- Multiple field type support -- State verification - -**Run:** `cargo run --example undo_redo` - -**Key Concepts:** -- ChangeCommand -- UndoStack management -- Command descriptions -- Redo stack truncation - ---- - -### 3. **Query Building & Data Access** - -#### `examples/query_builder.rs` -**Purpose:** Basic dynamic query builder - -**Features:** -- Filter collections using keypaths -- Chain multiple `where_()` predicates -- Count and execute queries -- Mutable query results - -**Run:** `cargo run --example query_builder` - -**Key Concepts:** -- Query builder -- Filter predicates -- execute() and execute_mut() - ---- - -#### `examples/advanced_query_builder.rs` -**Purpose:** Full-featured SQL-like query system - -**Features:** -- SELECT (projection) -- ORDER BY (ascending/descending) -- GROUP BY with aggregations -- LIMIT and pagination -- Aggregates (count, sum, avg, min, max) -- EXISTS queries -- Complex multi-stage queries - -**Run:** `cargo run --example advanced_query_builder` - -**Key Concepts:** -- LazyQuery -- Aggregation functions -- Float-specific sorting/aggregation -- Group-then-aggregate pattern - ---- - -#### `examples/join_query_builder.rs` -**Purpose:** SQL-like JOIN operations between collections - -**Features:** -- INNER JOIN -- LEFT JOIN -- Filtered joins (JOIN ... WHERE) -- Three-way joins -- Aggregated joins -- Self-joins - -**Run:** `cargo run --example join_query_builder` - -**Key Concepts:** -- JoinQuery -- Hash-based indexing for O(n) joins -- Generic mapper functions -- Multi-table queries - ---- - -### 4. **Container Support** - -#### `examples/container_adapters.rs` -**Purpose:** Working with smart pointers (Arc, Box, Rc) - -**Features:** -- `.for_arc()` adapter for `Vec>` -- `.for_box()` adapter for `Vec>` -- `.for_rc()` adapter for `Vec>` -- Filtering wrapped types -- Mutable access with Box -- Shared state patterns - -**Run:** `cargo run --example container_adapters` - -**Key Concepts:** -- Smart pointer adapters -- Zero-cost abstraction -- Immutable vs mutable containers - ---- - -#### `examples/reference_keypaths.rs` -**Purpose:** Working with collections of references - -**Features:** -- `.get_ref()` for `Vec<&T>` -- HashMap value references -- Nested references -- Performance comparison (owned vs references) -- Avoid cloning - -**Run:** `cargo run --example reference_keypaths` - -**Key Concepts:** -- Reference keypath access -- get_ref() and get_mut_ref() -- Zero-copy querying - ---- - -#### `key-paths-core/examples/container_adapter_test.rs` -**Purpose:** Comprehensive test suite for container adapters - -**Features:** -- 12 comprehensive tests -- Arc, Box, and Rc coverage -- Readable and writable paths -- Failable paths -- Value correctness verification - -**Run:** `cd key-paths-core && cargo run --example container_adapter_test` - ---- - -#### `key-paths-core/examples/reference_test.rs` -**Purpose:** Test suite for reference support - -**Features:** -- 8 comprehensive tests -- get_ref() verification -- get_mut_ref() verification -- Nested references -- Performance demonstration - -**Run:** `cd key-paths-core && cargo run --example reference_test` - ---- - -## 📊 Example Matrix - -| Example | Forms | Queries | Joins | Undo/Redo | Sync | Containers | References | -|---------|-------|---------|-------|-----------|------|------------|------------| -| user_form | ✅ | - | - | - | - | - | - | -| form_binding | ✅ | - | - | - | - | - | - | -| change_tracker | - | - | - | - | ✅ | - | - | -| undo_redo | - | - | - | ✅ | - | - | - | -| query_builder | - | ✅ | - | - | - | - | - | -| advanced_query_builder | - | ✅ | - | - | - | - | - | -| join_query_builder | - | ✅ | ✅ | - | - | - | - | -| container_adapters | - | ✅ | - | - | - | ✅ | - | -| reference_keypaths | - | ✅ | - | - | - | - | ✅ | - ---- - -## 🎯 Use Case Guide - -### "I need to validate form inputs" -→ [`user_form.rs`](examples/user_form.rs) or [`form_binding.rs`](examples/form_binding.rs) - -### "I need to track changes for synchronization" -→ [`change_tracker.rs`](examples/change_tracker.rs) - -### "I need undo/redo functionality" -→ [`undo_redo.rs`](examples/undo_redo.rs) - -### "I need to query in-memory data" -→ [`query_builder.rs`](examples/query_builder.rs) or [`advanced_query_builder.rs`](examples/advanced_query_builder.rs) - -### "I need to join multiple collections" -→ [`join_query_builder.rs`](examples/join_query_builder.rs) - -### "I have Vec> or other smart pointers" -→ [`container_adapters.rs`](examples/container_adapters.rs) - -### "I have Vec<&T> from HashMap.values()" -→ [`reference_keypaths.rs`](examples/reference_keypaths.rs) - ---- - -## 🚀 Quick Start - -### Run All Examples - -```bash -# Form examples -cargo run --example user_form -cargo run --example form_binding - -# State management -cargo run --example change_tracker -cargo run --example undo_redo - -# Queries -cargo run --example query_builder -cargo run --example advanced_query_builder -cargo run --example join_query_builder - -# Containers -cargo run --example container_adapters -cargo run --example reference_keypaths - -# Tests -cd key-paths-core -cargo run --example container_adapter_test -cargo run --example reference_test -``` - -### Example Complexity Levels - -**Beginner:** -1. `user_form.rs` - Simple form validation -2. `reference_keypaths.rs` - Reference support basics - -**Intermediate:** -3. `query_builder.rs` - Basic queries -4. `change_tracker.rs` - State synchronization -5. `container_adapters.rs` - Smart pointers - -**Advanced:** -6. `form_binding.rs` - Full binding system -7. `advanced_query_builder.rs` - SQL-like queries -8. `join_query_builder.rs` - Multi-table joins -9. `undo_redo.rs` - Command pattern - ---- - -## 💡 Common Patterns - -### Pattern 1: Form Validation -```rust -struct FormField { - path: KeyPaths, - validator: fn(&F) -> Result<(), String>, -} -``` - -### Pattern 2: Change Tracking -```rust -struct FieldChange { - path: Vec, - old_value: String, - new_value: String, -} -``` - -### Pattern 3: Query Building -```rust -Query::new(&data) - .where_(field_path, predicate) - .order_by(sort_path) - .limit(10) -``` - -### Pattern 4: Container Adapters -```rust -let items: Vec> = /* ... */; -let path = T::field_r().for_arc(); - -items.iter().filter(|item| { - path.get(item).map_or(false, |val| /* predicate */) -}) -``` - ---- - -## 📖 Learning Path - -1. **Start with:** `user_form.rs` - Learn basic keypath usage -2. **Then try:** `query_builder.rs` - Understand filtering -3. **Next:** `container_adapters.rs` - Learn adapters -4. **Reference:** `reference_keypaths.rs` - Optimize with references -5. **Advanced:** `join_query_builder.rs` - Multi-collection queries -6. **Expert:** `undo_redo.rs` - Complex state management - ---- - -## 🏆 Example Statistics - -- **Total Examples:** 11 (9 in examples/, 2 in key-paths-core/examples/) -- **Lines of Code:** ~3,500+ across all examples -- **Use Cases Covered:** 20+ -- **Container Types:** 5 (Arc, Box, Rc, &T, &mut T) -- **Query Operations:** 15+ (where, select, order, group, join, etc.) - ---- - -## 🔧 Development Tools - -### Expand Macros -```bash -cargo expand --example user_form -``` - -### Check a Specific Example -```bash -cargo check --example advanced_query_builder -``` - -### Run with Output Filter -```bash -cargo run --example join_query_builder 2>&1 | grep "Join" -``` - ---- - -## 📝 Contributing Examples - -When adding new examples: - -1. Add descriptive header comment with: - - Purpose - - Features demonstrated - - Run command - -2. Include diverse scenarios: - - Happy path - - Edge cases - - Error handling - -3. Use clear, descriptive names: - - Variables: `user_orders` not `uo` - - Functions: `create_profile_form` not `make_form` - -4. Add assertions where appropriate - -5. Run and verify: - ```bash - cargo run --example your_example - cargo fmt --all - ``` - ---- - -## 🎯 Quick Reference - -| Need | Example | Method | -|------|---------|---------| -| Filter data | `query_builder.rs` | `where_()` | -| Sort data | `advanced_query_builder.rs` | `order_by()` | -| Join tables | `join_query_builder.rs` | `inner_join()` | -| Track changes | `change_tracker.rs` | `detect_changes()` | -| Undo/redo | `undo_redo.rs` | `undo()`, `redo()` | -| Validate forms | `form_binding.rs` | `validate_all()` | -| Use Arc | `container_adapters.rs` | `.for_arc()` | -| Use &T | `reference_keypaths.rs` | `.get_ref()` | - ---- - -## 📚 Related Documentation - -- [`CONTAINER_ADAPTERS.md`](CONTAINER_ADAPTERS.md) - Complete container adapter guide -- [`REFERENCE_SUPPORT.md`](REFERENCE_SUPPORT.md) - Reference support documentation -- [`README.md`](README.md) - Main project documentation -- [`IMPLEMENTATION_SUMMARY.md`](IMPLEMENTATION_SUMMARY.md) - Implementation details - ---- - -## 🎉 Summary - -This collection of examples demonstrates the full power of rust-key-paths for: - -- ✅ Type-safe data access -- ✅ Generic form processing -- ✅ State management patterns -- ✅ In-memory query engines -- ✅ Change tracking systems -- ✅ Smart pointer support -- ✅ Zero-copy operations - -All examples are production-ready patterns that can be adapted for real-world applications! - diff --git a/EXAMPLES_TEST_RESULTS.md b/EXAMPLES_TEST_RESULTS.md deleted file mode 100644 index 818c077..0000000 --- a/EXAMPLES_TEST_RESULTS.md +++ /dev/null @@ -1,54 +0,0 @@ -# Examples Test Results - -## Summary - -After migrating from `Arc` to `Rc` and removing `Send + Sync` bounds: - -- **Total Examples**: 95 -- **Passed**: 94 ✅ -- **Failed**: 1 ❌ - -## Failed Example - -### `keypath_field_consumer_tool.rs` - -**Reason**: This example requires `Send + Sync` bounds because it implements a trait that requires these bounds: - -```rust -trait FieldAccessor: Send + Sync { - // ... -} -``` - -Since `KeyPaths` now uses `Rc` instead of `Arc`, it no longer implements `Send + Sync`, which is incompatible with this trait. - -**Solution Options**: -1. Remove `Send + Sync` requirement from the `FieldAccessor` trait (if single-threaded use is acceptable) -2. Use a different approach that doesn't require `Send + Sync` -3. Document that this example doesn't work with the Rc-based approach - -## All Other Examples Pass ✅ - -All 94 other examples compile and run successfully, demonstrating that: -- The migration to `Rc` is successful -- Removing `Send + Sync` bounds doesn't break existing functionality -- The API remains compatible for single-threaded use cases - -## Key Examples Verified - -- ✅ `basics_macros.rs` - Basic keypath usage -- ✅ `basics_casepath.rs` - Enum case paths -- ✅ `attribute_scopes.rs` - Attribute-based generation -- ✅ `deep_nesting_composition_example.rs` - Complex composition -- ✅ `arc_rwlock_aggregator_example.rs` - Arc support -- ✅ `failable_combined_example.rs` - Failable keypaths -- ✅ `derive_macros_new_features_example.rs` - New derive features -- ✅ `partial_any_aggregator_example.rs` - Type-erased keypaths -- ✅ All container adapter examples -- ✅ All composition examples -- ✅ All enum keypath examples - -## Conclusion - -The migration to `Rc` and removal of `Send + Sync` bounds is **successful** with 99% of examples working correctly. The single failing example requires `Send + Sync` for its specific use case, which is expected given the change from `Arc` to `Rc`. - diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 53df67a..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,129 +0,0 @@ -# Rust Key-Paths Library - Implementation Summary - -## 🎉 Successfully Implemented - -### ✅ **Complete Container Support** -The library now supports **ALL** major Rust standard library container types: - -#### Basic Containers -- `Option` - Failable access methods -- `Vec` - Indexed access methods -- `Box` - Direct access methods -- `Rc` - Direct access methods -- `Arc` - Direct access methods - -#### Collections -- `HashSet` - Element access methods -- `BTreeSet` - Element access methods -- `VecDeque` - Indexed access methods -- `LinkedList` - Indexed access methods -- `BinaryHeap` - Peek access methods - -#### Maps -- `HashMap` - Key-based access methods -- `BTreeMap` - Key-based access methods (limited) - -### ✅ **Generated KeyPath Methods** -For each field `field_name` with type `T`, the macro generates: - -```rust -// Direct access -field_name_r() -> KeyPaths // Readable -field_name_w() -> KeyPaths // Writable - -// Failable access (for Option-like types) -field_name_fr() -> KeyPaths // Failable readable -field_name_fw() -> KeyPaths // Failable writable - -// Indexed/Key-based access (for collections/maps) -field_name_fr_at(key) -> KeyPaths // Indexed readable -field_name_fw_at(key) -> KeyPaths // Indexed writable -``` - -### ✅ **Issues Fixed** -1. **BTreeMap Generic Constraints** - Fixed by removing problematic key-based methods -2. **BinaryHeap Type Issues** - Fixed by removing failable methods with `str`/`String` conflicts -3. **Type Variable Usage** - Fixed for all basic container types -4. **API Integration** - Proper integration with KeyPaths core library - -## ❌ **Remaining Limitations** - -### Nested Container Combinations -- `Box>`, `Option>`, etc. have type mismatch issues -- **Status**: Commented out in macro to prevent compilation errors -- **Next Steps**: Fix type variable usage in nested combination generation - -### Limited Methods for Some Types -- **BTreeMap**: No key-based access methods (generic constraint issues) -- **BinaryHeap**: No failable methods (type system conflicts) - -## 🧪 **Comprehensive Testing** - -### Test Coverage -- ✅ All basic types (`String`, `i32`, `bool`) -- ✅ All basic containers (`Option`, `Vec`, `Box`, `Rc`, `Arc`) -- ✅ All collections (`HashSet`, `BTreeSet`, `VecDeque`, `LinkedList`, `BinaryHeap`) -- ✅ All maps (`HashMap`, `BTreeMap`) -- ❌ Nested combinations (partially working) - -### Test Files Created -- `comprehensive_test_suite.rs` - Full test suite -- `all_containers_test.rs` - All container types -- `working_containers_test.rs` - Basic working cases -- Multiple debug files for specific issues - -## 📊 **Final Statistics** - -| Category | Total | Working | Status | -|----------|-------|---------|---------| -| Basic Types | 3 | 3 | ✅ 100% | -| Basic Containers | 5 | 5 | ✅ 100% | -| Collections | 5 | 5 | ✅ 100% | -| Maps | 2 | 2 | ✅ 100% | -| Nested Combinations | 6+ | 0 | ⏸️ Commented Out | - -**Overall Success Rate: 100% (15/15 supported container types)** - -## 🚀 **Usage Examples** - -```rust -use key_paths_derive::Keypaths; - -#[derive(Debug, Kp)] -struct MyStruct { - data: Vec, - config: Option>, - cache: Box>, -} - -fn main() { - // Direct access - let data_path = MyStruct::data_r(); - - // Failable access - let config_path = MyStruct::config_fr(); - - // Composition - let composed = MyStruct::config_fr() - .then(OtherStruct::field_r()); -} -``` - -## 🎯 **Next Steps** - -1. **Fix Nested Combinations** - Debug and fix type mismatch issues in commented-out code -2. **Add Key-Based Access** - Implement proper generic constraints for BTreeMap -3. **Add Failable Methods** - Fix BinaryHeap failable access -4. **Performance Testing** - Benchmark keypath performance -5. **Documentation** - Create comprehensive usage guide - -## 🏆 **Achievement Summary** - -The Rust Key-Paths library now provides **comprehensive support** for all major Rust standard library container types, making it a powerful tool for safe, composable data access. The library successfully handles: - -- **15+ container types** with full keypath generation -- **6 different keypath method types** per field -- **Composable keypaths** for complex data traversal -- **Type-safe access** to nested data structures - -This represents a significant expansion of the library's capabilities and makes it suitable for real-world Rust applications requiring safe data access patterns. diff --git a/INTEROPERABILITY.md b/INTEROPERABILITY.md deleted file mode 100644 index cd1ec89..0000000 --- a/INTEROPERABILITY.md +++ /dev/null @@ -1,437 +0,0 @@ -# Keypath Interoperability: AsyncLockKp ⇒ LockKp ⇒ Kp - -## Overview - -The keypath library now supports seamless interoperability between three keypath types, allowing you to chain through different lock levels and regular field access in a single navigation path: - -- **`Kp`**: Regular field access (no locks) -- **`LockKp`**: Synchronous locks (`Arc`, `Arc`, `Rc`) -- **`AsyncLockKp`**: Asynchronous locks (`Arc`, `Arc`) - -## Hierarchy - -``` -AsyncLockKp (top level - async locks) - ↓ -LockKp (middle level - sync locks) - ↓ -Kp (bottom level - regular fields) -``` - -## Interoperability Methods - -### 1. AsyncLockKp Methods - -#### `then()` - Chain with `Kp` - -Navigate through an async lock, then continue with regular field access. - -```rust -pub async fn then<'a, V2, Value2, MutValue2, G3, S3>( - &'a self, - next_kp: &'a crate::Kp, - root: Root, -) -> Option -``` - -**Example:** -```rust -use tokio::sync::Mutex; - -struct Root { - data: Arc>, -} - -struct Inner { - value: i32, -} - -// Create AsyncLockKp to navigate to Inner -let async_kp = AsyncLockKp::new( - Kp::new(|r: &Root| Some(&r.data), ...), // prev: Root -> Arc> - TokioMutexAccess::new(), // mid: unlock Inner - Kp::new(|i: &Inner| Some(i), ...), // next: Inner -> Inner -); - -// Create Kp to navigate to value field -let value_kp = Kp::new( - |i: &Inner| Some(&i.value), - |i: &mut Inner| Some(&mut i.value), -); - -// Chain: async lock -> field access -let result = async_kp.then(&value_kp, &root).await; -// result == Some(&42) -``` - -#### `later_then()` - Compose with another `AsyncLockKp` - -Navigate through multiple nested async locks. - -**Note**: Due to Rust's closure `Clone` constraints, this method takes the components of the second `AsyncLockKp` (prev, mid, next) separately rather than the struct itself. - -```rust -pub async fn later_then<'a, Lock2, Mid2, V2, ...>( - &'a self, - other_prev: crate::Kp, - other_mid: L2, - other_next: crate::Kp, - root: Root, -) -> Option -``` - -**Example:** -```rust -use tokio::sync::Mutex; - -struct Root { - lock1: Arc>, -} - -struct Container { - lock2: Arc>, -} - -// First AsyncLockKp: Root -> Container -let async_kp1 = AsyncLockKp::new( - Kp::new(|r: &Root| Some(&r.lock1), ...), - TokioMutexAccess::new(), - Kp::new(|c: &Container| Some(c), ...), -); - -// Second AsyncLockKp: Container -> i32 -let async_kp2 = AsyncLockKp::new( - Kp::new(|c: &Container| Some(&c.lock2), ...), - TokioMutexAccess::new(), - Kp::new(|n: &i32| Some(n), ...), -); - -// Compose: async lock1 -> async lock2 -// Pass components separately to avoid Clone bounds on closures -let result = async_kp1.later_then( - async_kp2.prev, - async_kp2.mid, - async_kp2.next, - &root -).await; -// result == Some(&999) -``` - -#### `then_lock_kp_get()` - Chain with `LockKp` - -Navigate through an async lock, then through a sync lock. - -```rust -pub async fn then_lock_kp_get<'a, Lock2, ...>( - &'a self, - lock_kp: &'a crate::lock::LockKp<...>, - root: Root, -) -> Option -``` - -**Example:** -```rust -use tokio::sync::Mutex as TokioMutex; -use std::sync::{Arc as StdArc, Mutex as StdMutex}; - -struct Root { - async_lock: Arc>, -} - -struct Container { - sync_lock: StdArc>, -} - -let async_kp = AsyncLockKp::new(...); // Root -> Container -let lock_kp = LockKp::new(...); // Container -> i32 - -// Chain: async lock -> sync lock -let result = async_kp.then_lock_kp_get(&lock_kp, &root).await; -``` - -#### `compose_async_get()` - Chain with another `AsyncLockKp` - -Navigate through multiple async locks. - -```rust -pub async fn compose_async_get<'a, Lock2, ...>( - &'a self, - other: &'a AsyncLockKp<...>, - root: Root, -) -> Option -``` - -**Example:** -```rust -struct Root { - lock1: Arc>, -} - -struct Container { - lock2: Arc>, -} - -let async_kp1 = AsyncLockKp::new(...); // Root -> Container -let async_kp2 = AsyncLockKp::new(...); // Container -> i32 - -// Chain: async lock1 -> async lock2 -let result = async_kp1.compose_async_get(&async_kp2, &root).await; -``` - -### 2. LockKp Methods - -#### `then_async_kp_get()` - Chain with `AsyncLockKp` - -Navigate through a sync lock, then through an async lock. - -```rust -#[cfg(feature = "tokio")] -pub async fn then_async_kp_get<'a, Lock2, ...>( - &'a self, - async_kp: &'a crate::async_lock::AsyncLockKp<...>, - root: Root, -) -> Option -``` - -**Example:** -```rust -use std::sync::{Arc, Mutex}; -use tokio::sync::Mutex as TokioMutex; - -struct Root { - sync_lock: Arc>, -} - -struct Container { - async_lock: Arc>, -} - -let lock_kp = LockKp::new(...); // Root -> Container -let async_kp = AsyncLockKp::new(...); // Container -> String - -// Chain: sync lock -> async lock -let result = lock_kp.then_async_kp_get(&async_kp, &root).await; -``` - -### 3. Kp Methods - -#### `then_lock_kp_get()` - Chain with `LockKp` - -Navigate to a field, then through a sync lock. - -```rust -pub fn then_lock_kp_get( - &self, - lock_kp: &crate::lock::LockKp<...>, - root: Root, -) -> Option -``` - -**Example:** -```rust -struct Root { - container: Container, -} - -struct Container { - sync_lock: Arc>, -} - -let regular_kp: KpType = Kp::new( - |r: &Root| Some(&r.container), - |r: &mut Root| Some(&mut r.container), -); - -let lock_kp = LockKp::new(...); // Container -> i32 - -// Chain: regular field -> sync lock -let result = regular_kp.then_lock_kp_get(&lock_kp, &root); -``` - -#### `then_async_kp_get()` - Chain with `AsyncLockKp` - -Navigate to a field, then through an async lock. - -```rust -#[cfg(feature = "tokio")] -pub async fn then_async_kp_get( - &self, - async_kp: &crate::async_lock::AsyncLockKp<...>, - root: Root, -) -> Option -``` - -**Example:** -```rust -struct Root { - container: Container, -} - -struct Container { - async_lock: Arc>, -} - -let regular_kp: KpType = Kp::new( - |r: &Root| Some(&r.container), - |r: &mut Root| Some(&mut r.container), -); - -let async_kp = AsyncLockKp::new(...); // Container -> String - -// Chain: regular field -> async lock -let result = regular_kp.then_async_kp_get(&async_kp, &root).await; -``` - -## Complete Example: Chaining All Three Levels - -```rust -use std::sync::{Arc as StdArc, Mutex as StdMutex}; -use tokio::sync::Mutex as TokioMutex; - -struct Root { - level1: Level1, // Regular field -} - -struct Level1 { - sync_lock: StdArc>, // Sync lock -} - -struct Level2 { - async_lock: Arc>, // Async lock -} - -#[tokio::main] -async fn main() { - let root = Root { - level1: Level1 { - sync_lock: StdArc::new(StdMutex::new(Level2 { - async_lock: Arc::new(TokioMutex::new(888)), - })), - }, - }; - - // Step 1: Regular Kp to navigate to Level1 - let kp: KpType = Kp::new( - |r: &Root| Some(&r.level1), - |r: &mut Root| Some(&mut r.level1), - ); - - // Step 2: LockKp through sync lock to Level2 - let lock_kp = LockKp::new( - Kp::new(|l1: &Level1| Some(&l1.sync_lock), ...), - ArcMutexAccess::new(), - Kp::new(|l2: &Level2| Some(l2), ...), - ); - - // Step 3: AsyncLockKp through async lock to i32 - let async_kp = AsyncLockKp::new( - Kp::new(|l2: &Level2| Some(&l2.async_lock), ...), - TokioMutexAccess::new(), - Kp::new(|n: &i32| Some(n), ...), - ); - - // Chain all three: Kp -> LockKp -> AsyncLockKp - let level1 = kp.get(&root).unwrap(); - let level2 = lock_kp.get(level1).unwrap(); - let result = async_kp.get_async(level2).await; - - assert_eq!(result, Some(&888)); -} -``` - -## Method Naming Convention - -| From | To | Method Name | Description | -|------|-----|-------------|-------------| -| AsyncLockKp | Kp | `then()` | Chain to regular field | -| AsyncLockKp | AsyncLockKp | `later_then()` | Compose with another async lock | -| AsyncLockKp | LockKp | `then_lock_kp_get()` | Chain to sync lock | -| AsyncLockKp | AsyncLockKp | `compose_async_get()` | Chain to another async lock (alternative) | -| LockKp | AsyncLockKp | `then_async_kp_get()` | Chain to async lock | -| Kp | LockKp | `then_lock_kp_get()` | Chain to sync lock | -| Kp | AsyncLockKp | `then_async_kp_get()` | Chain to async lock | - -## Design Principles - -### 1. **Explicit Chaining** -Each chain operation requires explicitly calling a method with the next keypath. This makes the navigation path clear and the lock acquisitions visible. - -### 2. **Async Awareness** -- Methods that cross into async territory return `async fn` and require `.await` -- Methods staying in sync world remain synchronous - -### 3. **Lifetime Management** -All chaining methods use lifetime parameters (`'a`) to ensure the keypaths and their closures live long enough for the operation. - -### 4. **No Automatic Composition** -Unlike the `then()` and `compose()` methods on individual keypath types that return new composed keypaths, the interoperability methods directly execute the navigation and return the value. This avoids complex lifetime and `Clone` bound issues with closures. - -## Performance Characteristics - -### Shallow Cloning Guarantee -All interoperability operations maintain the shallow cloning guarantee: -- `Arc` clones only increment reference counts -- Lock accessor structs (`TokioMutexAccess`, etc.) contain only `PhantomData` (zero-cost) -- Function pointers are cheap to copy -- **No deep data cloning occurs** - -### Lock Acquisition -- Sync locks block the current thread -- Async locks yield to the executor (non-blocking) -- Chaining multiple locks acquires them sequentially (as needed by the navigation path) - -## Tests - -The interoperability is tested with: - -1. **`test_async_kp_then`** (async_lock.rs): AsyncLockKp -> Kp -2. **`test_kp_then_lock_kp`** (lib.rs): Kp -> LockKp -3. **`test_kp_then_async_kp`** (lib.rs): Kp -> AsyncLockKp -4. **`test_full_chain_kp_to_lock_to_async`** (lib.rs): Kp -> LockKp -> AsyncLockKp (full chain) -5. **`test_lock_kp_then_async_kp`** (lock.rs): LockKp -> AsyncLockKp - -Total: **82 tests passing** (including all interoperability tests) - -## Limitations - -### No Automatic Type Inference -Due to Rust's type system, you often need to provide type annotations when creating the intermediate keypaths: - -```rust -let value_kp: KpType = Kp::new( - |i: &Inner| Some(&i.value), - |i: &mut Inner| Some(&mut i.value), -); -``` - -### Async Propagation -Once you enter async territory (via `AsyncLockKp` or `then_async_kp_get()`), all subsequent operations must be `async` and require `.await`. - -### Clone Bounds -The `AsyncLockKp` struct requires `Clone` bounds on its function types (`G1`, `S1`, etc.), which means closures that capture non-`Clone` data cannot be used. Use function pointers or ensure captured data is `Clone`. - -## Future Enhancements - -Potential improvements for interoperability: - -1. **Builder Pattern**: Fluent API for chaining - ```rust - ChainBuilder::new() - .with_kp(regular_kp) - .with_lock_kp(lock_kp) - .with_async_kp(async_kp) - .execute(&root) - .await - ``` - -2. **Macro Support**: Generate interoperability chains from a declarative syntax - ```rust - chain!(root => field => sync_lock => async_lock => value) - ``` - -3. **Composed Return Types**: Return new composed keypaths instead of directly executing - (Currently challenging due to closure lifetime and `Clone` constraints) - -## Conclusion - -The interoperability feature enables seamless navigation through complex data structures with mixed synchronization strategies, maintaining type safety, shallow cloning guarantees, and explicit lock acquisition points throughout the navigation path. diff --git a/KP_DERIVE_SUMMARY.md b/KP_DERIVE_SUMMARY.md deleted file mode 100644 index 9d90992..0000000 --- a/KP_DERIVE_SUMMARY.md +++ /dev/null @@ -1,260 +0,0 @@ -# Kp Derive Macro - Implementation Summary - -## Overview - -The `#[derive(Kp)]` macro has been extended to intelligently handle all wrapper types supported in the rust-keypaths library. It generates keypath methods that automatically unwrap, dereference, or access elements from various container types. - -## Supported Types - -### Basic Types -- Direct field access for primitives and structs -- Generated method: `field_name() -> KpType<'static, Root, FieldType>` - -### Smart Container Handling - -#### Option -- Automatically unwraps `Option` to access inner `T` -- Returns `None` if the `Option` is `None` -- Example: `Option` → keypath returns `Option<&String>` - -#### Vec -- Accesses the **first element** of the vector -- Returns `None` if vector is empty -- Supports mutable access via `first_mut()` -- Example: `Vec` → keypath returns `Option<&Person>` (first person) - -#### Box -- Automatically dereferences to access inner `T` -- Supports both read and mutable access -- Example: `Box` → keypath returns `Option<&i32>` - -#### Arc / Rc -- Dereferences to access inner `T` -- **Read-only** - mutable access returns `None` -- Example: `Arc` → keypath returns `Option<&String>` - -#### HashMap / BTreeMap -- Returns keypath to the **container itself** -- Allows further operations on the map -- Example: `HashMap` → keypath returns `Option<&HashMap>` - -#### HashSet / BTreeSet -- Accesses **any element** via `.iter().next()` -- **Read-only** - no mutable iteration support -- Example: `HashSet` → keypath returns `Option<&String>` - -#### VecDeque -- Accesses the **front element** -- Supports mutable access via `front_mut()` -- Example: `VecDeque` → keypath returns `Option<&i32>` - -#### LinkedList -- Accesses the **front element** -- Supports mutable access via `front_mut()` -- Example: `LinkedList` → keypath returns `Option<&i32>` - -#### BinaryHeap -- Accesses the **top element** via `peek()` -- **Read-only** - no mutable peek support -- Example: `BinaryHeap` → keypath returns `Option<&i32>` - -#### Result -- Accesses the **Ok value** -- Returns `None` if the result is `Err` -- Supports mutable access -- Example: `Result` → keypath returns `Option<&String>` - -#### Mutex / RwLock -- Returns keypath to the **container itself** -- Does not automatically lock - user must lock explicitly -- Supports both `std::sync` and `parking_lot` variants -- Example: `Mutex` → keypath returns `Option<&Mutex>` - -#### Weak -- Returns keypath to the **container itself** -- **Read-only** - no mutable access -- Example: `Weak` → keypath returns `Option<&Weak>` - -### Enum Support - -The derive macro now supports enums with different variant types: - -#### Unit Variants -```rust -#[derive(Kp)] -enum Status { - Active, - Inactive, -} - -// Generates: -// Status::active() -> checks if enum matches Active variant -``` - -#### Single-Field Tuple Variants -```rust -#[derive(Kp)] -enum Data { - Text(String), - Number(i32), -} - -// Generates: -// Data::text() -> returns Option<&String> if variant is Text -// Data::number() -> returns Option<&i32> if variant is Number -``` - -Smart unwrapping applies to tuple variants too: -```rust -#[derive(Kp)] -enum OptionalData { - Value(Option), -} - -// Generates: -// OptionalData::value() -> returns Option<&String> (unwraps the inner Option) -``` - -#### Multi-Field Tuple Variants -```rust -#[derive(Kp)] -enum Complex { - Pair(i32, String), -} - -// Generates: -// Complex::pair() -> returns Option<&Complex> (the variant itself) -``` - -#### Named Field Variants -```rust -#[derive(Kp)] -enum Point { - TwoD { x: f64, y: f64 }, -} - -// Generates: -// Point::two_d() -> returns Option<&Point> (the variant itself) -``` - -### Tuple Struct Support - -```rust -#[derive(Kp)] -struct Coords(f64, f64, Option); - -// Generates: -// Coords::f0() -> access first field (f64) -// Coords::f1() -> access second field (f64) -// Coords::f2() -> access third field, unwrapping Option -``` - -## Usage Examples - -### Basic Struct -```rust -#[derive(Kp)] -struct Person { - name: String, - age: i32, -} - -let person = Person { name: "Alice".to_string(), age: 30 }; -let name_kp = Person::name(); -let name = name_kp.get(&person); // Some(&"Alice") -``` - -### With Option -```rust -#[derive(Kp)] -struct User { - email: Option, -} - -let user = User { email: Some("user@example.com".to_string()) }; -let email_kp = User::email(); -let email = email_kp.get(&user); // Some(&"user@example.com") -``` - -### With Vec -```rust -#[derive(Kp)] -struct Company { - employees: Vec, -} - -let company = Company { - employees: vec![ - Person { name: "Alice".to_string(), age: 30 }, - Person { name: "Bob".to_string(), age: 25 }, - ], -}; - -let employees_kp = Company::employees(); -let first_employee = employees_kp.get(&company); // Some(&Person { name: "Alice", age: 30 }) -``` - -### Mutable Access -```rust -let mut person = Person { name: "Bob".to_string(), age: 25 }; -let age_kp = Person::age(); - -age_kp.get_mut(&mut person).map(|age| *age = 26); -assert_eq!(person.age, 26); -``` - -## Implementation Details - -### Type Introspection -The macro uses `extract_wrapper_inner_type()` to detect wrapper types by examining the `syn::Type` structure. It identifies: -- Single-parameter generics (Option, Box, Vec, etc.) -- Two-parameter generics (HashMap, BTreeMap) -- Nested combinations (Option>, Arc>, etc.) -- Synchronization primitive variants (std::sync vs parking_lot) - -### Code Generation Strategy -For each field: -1. **Analyze the field type** to determine wrapper kind -2. **Generate appropriate getter** that handles unwrapping/dereferencing -3. **Generate appropriate mutable getter** (or return `None` if not supported) -4. **Return `KpType<'static, Root, Value>`** where `Value` is the unwrapped type - -### Naming Convention -- Struct named fields: use field name directly (`Person::name()`) -- Tuple struct fields: use `f0()`, `f1()`, `f2()`, etc. -- Enum variants: convert to snake_case (`MyVariant` → `my_variant()`) - -## Testing - -Comprehensive test coverage includes: -- ✅ Basic field access (5 tests) -- ✅ Wrapper type handling (18 tests) - - Option (with Some and None) - - Vec, Box, Arc, Rc - - Result (with Ok and Err) - - HashMap, BTreeMap, HashSet, BTreeSet - - VecDeque, LinkedList, BinaryHeap - - Mutex, RwLock -- ✅ Enum variants (unit, single-field, multi-field, named) -- ✅ Tuple structs -- ✅ Mutable access -- ✅ Type signatures - -All 23 tests pass successfully. - -## Comparison with Reference Implementation - -The `Kp` derive macro implementation closely mirrors the reference `Keypath` derive from `keypaths-proc/src/lib.rs` (lines 4818-5353), with these key differences: - -1. **Return Type**: Uses `KpType` instead of `OptionalKeyPath` -2. **API**: Single method per field instead of separate `_r()`, `_w()`, `_fr()`, `_fw()` methods -3. **Simplicity**: Combined read/write access in one keypath -4. **Composability**: Designed for keypath composition via `.then()` method - -## Future Enhancements - -Potential improvements: -- Support for nested wrapper combinations (Option>, Arc>) -- Custom unwrapping strategies via attributes -- Index-based access for Vec/arrays (`#[kp(index)]`) -- Named field access for enum variants with named fields diff --git a/LOCK_KP_GUIDE.md b/LOCK_KP_GUIDE.md deleted file mode 100644 index 2d2e61d..0000000 --- a/LOCK_KP_GUIDE.md +++ /dev/null @@ -1,454 +0,0 @@ -# LockKp - Keypath for Locked Values - -## Overview - -`LockKp` is a specialized keypath type for handling locked/synchronized values like `Arc>` and `Arc>`. It provides a safe, composable way to navigate through locked data structures, including support for **multi-level lock chaining**. - -## Structure - -The `LockKp` struct has three key components: - -```rust -pub struct LockKp<...> { - /// Keypath from Root to Lock container (e.g., Arc>) - pub prev: Kp, - - /// Lock access handler (trait-based, converts Lock -> Inner) - pub mid: L, // implements LockAccess trait - - /// Keypath from Inner to final Value - pub next: Kp, -} -``` - -### The Three Fields: - -1. **`prev`**: A regular `Kp` that navigates from Root to the Lock container -2. **`mid`**: A trait object implementing `LockAccess` that handles lock/unlock operations -3. **`next`**: A regular `Kp` that navigates from the Inner type to the final Value - -## LockAccess Trait - -The `mid` field uses the `LockAccess` trait, which defines how to access locked values: - -```rust -pub trait LockAccess { - fn lock_read(&self, lock: &Lock) -> Option; - fn lock_write(&self, lock: &mut Lock) -> Option; -} -``` - -## Lock Implementations - -### ArcMutexAccess - -Standard implementation for `Arc>`: -```rust -let lock_kp = LockKp::new(prev, ArcMutexAccess::new(), next); -``` - -**Use when:** -- Multi-threaded environment -- Simple exclusive access needed -- Default choice for thread-safe locks -- Standard library is sufficient - -### ArcRwLockAccess - -Implementation for `Arc>`: -```rust -let lock_kp = LockKp::new(prev, ArcRwLockAccess::new(), next); -``` - -**Use when:** -- Multi-threaded environment -- Multiple concurrent readers needed -- Read-heavy workloads -- Want to allow parallel read access - -**RwLock Semantics:** -- Multiple readers can access simultaneously (shared/immutable) -- Only one writer can access at a time (exclusive/mutable) -- Readers and writers are mutually exclusive - -### ParkingLotMutexAccess (Feature: `parking_lot`) - -High-performance implementation for `Arc>`: -```rust -let lock_kp = LockKp::new(prev, ParkingLotMutexAccess::new(), next); -``` - -**Use when:** -- High-contention scenarios where performance matters -- Want fair lock acquisition (FIFO, no writer starvation) -- Don't need lock poisoning semantics -- Need predictable latency - -**Advantages over std::Mutex:** -- **~2-3x faster** in many scenarios -- Only 1 byte of overhead vs platform-dependent size -- Fair locking algorithm (FIFO) -- No poisoning on panic -- Better performance under high contention - -### ParkingLotRwLockAccess (Feature: `parking_lot`) - -High-performance implementation for `Arc>`: -```rust -let lock_kp = LockKp::new(prev, ParkingLotRwLockAccess::new(), next); -``` - -**Use when:** -- Read-heavy workloads with occasional writes -- High-performance requirements -- Need predictable writer scheduling -- Want writer-preferring behavior to prevent writer starvation - -**Advantages over std::RwLock:** -- **Significantly faster** for both read and write operations -- More compact memory representation -- Writer-preferring (prevents writer starvation) -- No poisoning on panic -- Optional deadlock detection in debug builds -- Better scalability with many readers - -### RcRefCellAccess ⭐ NEW - -Implementation for `Rc>` (single-threaded): -```rust -let lock_kp = LockKp::new(prev, RcRefCellAccess::new(), next); -``` - -**Use when:** -- Single-threaded context only -- Want lower overhead than Arc/Mutex -- Don't need thread safety -- Need interior mutability without atomic operations - -**RefCell Semantics:** -- Multiple immutable borrows allowed simultaneously -- Only one mutable borrow allowed at a time -- Runtime borrow checking (panics on violation) -- **NOT thread-safe** - use only in single-threaded code - -| Feature | Arc | Arc | parking_lot::Mutex | parking_lot::RwLock | Rc | -|---------|------------|-------------|-------------------|---------------------|-------------| -| Multiple readers | ❌ Blocked | ✅ Concurrent | ❌ Blocked | ✅ Concurrent | ✅ Concurrent | -| Write access | ✅ Exclusive | ✅ Exclusive | ✅ Exclusive | ✅ Exclusive | ✅ Exclusive | -| Thread-safe | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No (single-threaded) | -| Performance | Moderate | Moderate | **High** | **High** | Very Low overhead | -| Overhead | Platform-dep | Moderate | **1 byte** | Low | Very Low | -| Atomic ops | Yes | Yes | Yes | Yes | No | -| Fair scheduling | No | No | **✅ FIFO** | **✅ Writer-pref** | N/A | -| Poisoning | Yes | Yes | **❌ No** | **❌ No** | Runtime panic | -| Best for | Multi-threaded | Read-heavy | High-contention | Read-heavy perf | Single-threaded | -| Use when | Default (threaded) | Many readers | Need speed/fairness | Need speed/no starvation | No threads needed | - -## Usage Examples - -### Single Lock Level - -```rust -use std::sync::{Arc, Mutex}; - -#[derive(Clone)] -struct Root { - locked_data: Arc>, -} - -#[derive(Clone)] -struct Inner { - value: String, -} - -// Create the three components -let prev: KpType>> = Kp::new( - |r: &Root| Some(&r.locked_data), - |r: &mut Root| Some(&mut r.locked_data), -); - -let mid = ArcMutexAccess::new(); - -let next: KpType = Kp::new( - |i: &Inner| Some(&i.value), - |i: &mut Inner| Some(&mut i.value), -); - -// Combine them into a LockKp -let lock_kp = LockKp::new(prev, mid, next); - -// Get value through the lock -let value = lock_kp.get(&root); -``` - -### RwLock Example - -```rust -use std::sync::{Arc, RwLock}; - -let lock_kp = LockKp::new( - Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)), - ArcRwLockAccess::new(), // ← Use RwLock for concurrent reads - Kp::new(|i: &Inner| Some(&i.value), |i: &mut Inner| Some(&mut i.value)), -); -``` - -### Rc Example (Single-threaded) - -```rust -use std::rc::Rc; -use std::cell::RefCell; - -let lock_kp = LockKp::new( - Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)), - RcRefCellAccess::new(), // ← Use Rc for single-threaded - Kp::new(|i: &Inner| Some(&i.value), |i: &mut Inner| Some(&mut i.value)), -); -``` - -## Chaining with `then()` - -`LockKp` supports chaining with regular `Kp` using the `then()` operator: - -```rust -// Root -> Arc> -> Inner1 -> Inner2 -> Value -let chained = lock_kp.then(another_kp); -``` - -## Multi-Level Lock Chaining with `compose()` - -The `compose()` method allows you to chain through **multiple lock levels**: - -```rust -// Root -> Lock1 -> Mid1 -> Lock2 -> Mid2 -> Value -let composed = lock_kp1.compose(lock_kp2); -``` - -### Two-Level Example - -```rust -#[derive(Clone)] -struct Root { - level1: Arc>, -} - -#[derive(Clone)] -struct Level1 { - level2: Arc>, -} - -#[derive(Clone)] -struct Level2 { - value: String, -} - -// First lock level: Root -> Level1 -let lock1 = LockKp::new( - Kp::new(|r: &Root| Some(&r.level1), |r: &mut Root| Some(&mut r.level1)), - ArcMutexAccess::new(), - Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)), -); - -// Second lock level: Level1 -> Level2 -> String -let lock2 = LockKp::new( - Kp::new(|l: &Level1| Some(&l.level2), |l: &mut Level1| Some(&mut l.level2)), - ArcMutexAccess::new(), - Kp::new(|l: &Level2| Some(&l.value), |l: &mut Level2| Some(&mut l.value)), -); - -// Compose both locks -let composed = lock1.compose(lock2); - -// Navigate through both lock levels in one operation -let value = composed.get(&root); -``` - -### Three-Level Example - -You can compose multiple times for deeply nested locks: - -```rust -// Root -> Lock1 -> L1 -> Lock2 -> L2 -> Lock3 -> L3 -> Value -let composed_1_2 = lock_kp1.compose(lock_kp2); -let composed_all = composed_1_2.compose(lock_kp3); - -// Navigate through all three lock levels -let value = composed_all.get(&root); -``` - -### Mixed Lock Types - -Compose different lock types together: -```rust -// RwLock -> Mutex composition -let rwlock_kp = LockKp::new(prev1, ArcRwLockAccess::new(), next1); -let mutex_kp = LockKp::new(prev2, ArcMutexAccess::new(), next2); -let mixed = rwlock_kp.compose(mutex_kp); -``` - -### Combining `compose()` and `then()` - -You can mix lock composition with regular keypath chaining: - -```rust -// Root -> Lock1 -> Lock2 -> Inner -> Data -> Value -let composed = lock1.compose(lock2); // Handle both locks -let with_data = composed.then(to_data); // Navigate to Data -let with_value = with_data.then(to_value); // Navigate to Value - -let value = with_value.get(&root); -``` - -## API Methods - -### `new()` -Creates a new `LockKp` from its three components: -```rust -pub fn new(prev: Kp<...>, mid: L, next: Kp<...>) -> Self -``` - -### `get()` -Gets the value through the lock: -```rust -pub fn get(&self, root: Root) -> Option -``` - -### `get_mut()` -Gets mutable access to the value through the lock: -```rust -pub fn get_mut(&self, root: MutRoot) -> Option -``` - -### `set()` -Sets the value through the lock using an updater function: -```rust -pub fn set(&self, root: Root, updater: F) -> Result<(), String> -where - F: FnOnce(&mut V), -``` - -### `then()` -Chains this LockKp with another regular Kp: -```rust -pub fn then(self, next_kp: Kp) -> LockKp -``` - -### `compose()` ⭐ -Composes this LockKp with another LockKp for multi-level lock chaining: -```rust -pub fn compose( - self, - other: LockKp -) -> LockKp -``` - -## Tests - -The module includes comprehensive tests: - -### Mutex Tests -- `test_lock_kp_basic`: Basic Mutex functionality -- `test_lock_kp_structure`: Verifies the three-field structure -- `test_lock_kp_then_chaining`: Tests chaining with `then()` -- `test_lock_kp_compose_single_level`: Tests composing two LockKps -- `test_lock_kp_compose_two_levels`: Tests two-level lock composition -- `test_lock_kp_compose_three_levels`: Tests three-level lock composition -- `test_lock_kp_compose_with_then`: Tests mixing `compose()` and `then()` - -### RwLock Tests -- `test_rwlock_basic`: Basic RwLock functionality -- `test_rwlock_compose_two_levels`: Two-level RwLock composition -- `test_rwlock_mixed_with_mutex`: RwLock and Mutex composition -- `test_rwlock_structure`: Verifies RwLock structure -- `test_rwlock_three_levels`: Three-level RwLock composition - -### Rc Tests (Single-threaded) ⭐ NEW -- `test_rc_refcell_basic`: Basic Rc functionality -- `test_rc_refcell_compose_two_levels`: Two-level Rc composition -- `test_rc_refcell_three_levels`: Three-level Rc composition -- `test_rc_refcell_panic_on_clone_proof`: Panic-on-clone proof for Rc -- `test_rc_refcell_vs_arc_mutex`: API comparison between Rc and Arc - -### Parking Lot Tests (Feature: `parking_lot`) ⭐ NEW -- `test_parking_lot_mutex_basic`: Basic parking_lot::Mutex functionality -- `test_parking_lot_rwlock_basic`: Basic parking_lot::RwLock functionality -- `test_parking_lot_mutex_compose`: Two-level parking_lot::Mutex composition -- `test_parking_lot_rwlock_write`: parking_lot::RwLock write access -- `test_parking_lot_panic_on_clone_proof`: Panic-on-clone proof for parking_lot::Mutex - -### Shallow Cloning Proof Tests ⭐ CRITICAL -- **`test_rwlock_panic_on_clone_proof`**: Uses `PanicOnClone` struct in nested RwLocks - **test PASSES** proving NO deep cloning -- **`test_mutex_panic_on_clone_proof`**: Uses `PanicOnClone` struct (1MB each level) in nested Mutexes - **test PASSES** proving NO deep cloning -- **`test_mixed_locks_panic_on_clone_proof`**: Uses `NeverClone` struct in mixed RwLock/Mutex chain - **test PASSES** proving NO deep cloning -- **`test_parking_lot_panic_on_clone_proof`**: Uses `PanicOnClone` struct with parking_lot::Mutex - **test PASSES** proving NO deep cloning - -**These tests are definitive proof**: Each test contains structs with a `Clone` impl that **PANICS with an error message**. The tests compose multiple lock levels and call `get()` multiple times. If ANY deep cloning occurred, the panic would trigger with a clear error message and tests would fail. - -✅ **All tests pass = Zero deep cloning confirmed!** - -What these tests verify: -- Composing 2-level RwLocks: No `Level1` or `Level2` cloning -- Composing 2-level Mutexes with 1MB data at each level: No `Mid` or `Inner` cloning -- Mixed RwLock→Mutex composition: No `Mid` or `Inner` cloning -- Multiple `get()` calls: Consistent shallow behavior -- Only `Arc` refcounts are incremented (shallow), never the inner data -- parking_lot locks maintain the same shallow cloning guarantee - -**All 88 tests pass** (63 from core library + 25 lock module tests) with `tokio` and `parking_lot` features enabled. - -## Integration - -The `lock` module is exported from the main library: - -```rust -use rust_key_paths::{LockKp, LockAccess, ArcMutexAccess, ArcRwLockAccess, RcRefCellAccess, LockKpType}; -``` - -## Custom Lock Types - -You can create custom lock access implementations for other synchronization primitives: - -```rust -#[derive(Clone)] -pub struct MyCustomLock { - _phantom: std::marker::PhantomData, -} - -impl<'a, T: 'static> LockAccess, &'a T> for MyCustomLock { - fn lock_read(&self, lock: &MyLockType) -> Option<&'a T> { - // Custom locking logic - } - - fn lock_write(&self, lock: &mut MyLockType) -> Option<&'a T> { - // Custom locking logic - } -} -``` - -## Benefits - -1. **Type-Safe**: Maintains type safety throughout the chain -2. **Composable**: Works seamlessly with regular `Kp` via `then()` -3. **Multi-Level**: Handles deeply nested locks via `compose()` -4. **Flexible**: Trait-based `mid` allows custom lock implementations -5. **Ergonomic**: Handles the complexity of locking internally -6. **Shallow Cloning**: Guaranteed shallow clones only (proven by panic tests) -7. **Zero Deep Copies**: Inner data is never cloned, only Arc refcounts - -## Use Cases - -### Single Lock -Simple synchronized access to shared data. - -### Nested Locks (via `compose()`) -- Configuration with nested locked sections -- Hierarchical data structures with locks at each level -- Multi-threaded systems with layered synchronization -- Game engines with nested entity locks -- Database connection pools with nested transaction locks - -### Mixed Chains (via `compose()` + `then()`) -Navigate through locks and then continue with regular field access. - -### Mixed Lock Types -Combine Mutex and RwLock based on access patterns at each level. diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index be435ed..0000000 --- a/MIGRATION.md +++ /dev/null @@ -1,425 +0,0 @@ -# Migration Guide: From `key-paths-core` to `rust-keypaths` - -This guide helps you migrate from the dynamic dispatch `key-paths-core` (v1.6.0) to the static dispatch `rust-keypaths` (v1.0.0) implementation. - -## Overview - -**rust-keypaths** is a static dispatch, faster alternative to `key-paths-core`. It provides: -- ✅ **Better Performance**: Write operations can be faster than manual unwrapping at deeper nesting levels -- ✅ **Zero Runtime Overhead**: No dynamic dispatch costs -- ✅ **Better Compiler Optimizations**: Static dispatch allows more aggressive inlining -- ✅ **Type Safety**: Full compile-time type checking with zero runtime cost - -## When to Migrate - -### ✅ Migrate to `rust-keypaths` if you: -- Want the best performance -- Don't need `Send + Sync` bounds -- Are starting a new project -- Want better compiler optimizations -- Don't need dynamic dispatch with trait objects - -### ⚠️ Stay with `key-paths-core` v1.6.0 if you: -- Need `Send + Sync` bounds for multithreaded scenarios -- Require dynamic dispatch with trait objects -- Have existing code using the enum-based `KeyPaths` API -- Need compatibility with older versions - -## Installation Changes - -### Before (key-paths-core) -```toml -[dependencies] -key-paths-core = "1.7.0" -key-paths-derive = "1.1.0" -``` - -### After (rust-keypaths) -```toml -[dependencies] -rust-keypaths = "1.0.0" -keypaths-proc = "1.0.0" -``` - -## API Changes - -### Core Types - -#### Before: Enum-based API -```rust -use key_paths_core::KeyPaths; - -// Readable keypath -let kp: KeyPaths = KeyPaths::readable(|s: &Struct| &s.field); - -// Failable readable keypath -let opt_kp: KeyPaths = KeyPaths::failable_readable(|s: &Struct| s.opt_field.as_ref()); - -// Writable keypath -let writable_kp: KeyPaths = KeyPaths::writable(|s: &mut Struct| &mut s.field); - -// Failable writable keypath -let fw_kp: KeyPaths = KeyPaths::failable_writable(|s: &mut Struct| s.opt_field.as_mut()); -``` - -#### After: Type-based API -```rust -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -// Readable keypath -let kp = KeyPath::new(|s: &Struct| &s.field); - -// Failable readable keypath -let opt_kp = OptionalKeyPath::new(|s: &Struct| s.opt_field.as_ref()); - -// Writable keypath -let writable_kp = WritableKeyPath::new(|s: &mut Struct| &mut s.field); - -// Failable writable keypath -let fw_kp = WritableOptionalKeyPath::new(|s: &mut Struct| s.opt_field.as_mut()); -``` - -### Access Methods - -#### Before -```rust -use key_paths_core::KeyPaths; - -let kp = KeyPaths::readable(|s: &User| &s.name); -let value = kp.get(&user); // Returns Option<&String> -``` - -#### After -```rust -use rust_keypaths::KeyPath; - -let kp = KeyPath::new(|s: &User| &s.name); -let value = kp.get(&user); // Returns &String (direct, not Option) -``` - -### Optional KeyPaths - -#### Before -```rust -use key_paths_core::KeyPaths; - -let opt_kp = KeyPaths::failable_readable(|s: &User| s.email.as_ref()); -let email = opt_kp.get(&user); // Returns Option<&String> -``` - -#### After -```rust -use rust_keypaths::OptionalKeyPath; - -let opt_kp = OptionalKeyPath::new(|s: &User| s.email.as_ref()); -let email = opt_kp.get(&user); // Returns Option<&String> (same) -``` - -### Chaining KeyPaths - -#### Before -```rust -use key_paths_core::KeyPaths; - -let kp1 = KeyPaths::failable_readable(|s: &Root| s.level1.as_ref()); -let kp2 = KeyPaths::failable_readable(|l1: &Level1| l1.level2.as_ref()); -let chained = kp1.compose(kp2); -``` - -#### After -```rust -use rust_keypaths::OptionalKeyPath; - -let kp1 = OptionalKeyPath::new(|s: &Root| s.level1.as_ref()); -let kp2 = OptionalKeyPath::new(|l1: &Level1| l1.level2.as_ref()); -let chained = kp1.then(kp2); // Note: method name changed from compose() to then() -``` - -### Writable KeyPaths - -#### Before -```rust -use key_paths_core::KeyPaths; - -let mut user = User { name: "Akash".to_string() }; -let kp = KeyPaths::writable(|s: &mut User| &mut s.name); -if let Some(name_ref) = kp.get_mut(&mut user) { - *name_ref = "Bob".to_string(); -} -``` - -#### After -```rust -use rust_keypaths::WritableKeyPath; - -let mut user = User { name: "Akash".to_string() }; -let kp = WritableKeyPath::new(|s: &mut User| &mut s.name); -let name_ref = kp.get_mut(&mut user); // Returns &mut String directly (not Option) -*name_ref = "Bob".to_string(); -``` - -### Container Unwrapping - -#### Before -```rust -use key_paths_core::KeyPaths; - -let kp = KeyPaths::failable_readable(|s: &Container| s.boxed.as_ref()); -let unwrapped = kp.for_box::(); // Required explicit type parameter -``` - -#### After -```rust -use rust_keypaths::OptionalKeyPath; - -let kp = OptionalKeyPath::new(|s: &Container| s.boxed.as_ref()); -let unwrapped = kp.for_box(); // Type automatically inferred! -``` - -### Enum Variant Extraction - -#### Before -```rust -use key_paths_core::KeyPaths; - -let enum_kp = KeyPaths::readable_enum(|e: &MyEnum| { - match e { - MyEnum::Variant(v) => Some(v), - _ => None, - } -}); -``` - -#### After -```rust -use rust_keypaths::EnumKeyPaths; - -let enum_kp = EnumKeyPaths::for_variant(|e: &MyEnum| { - if let MyEnum::Variant(v) = e { - Some(v) - } else { - None - } -}); -``` - -### Proc Macros - -#### Before -```rust -use key_paths_derive::Keypaths; - -#[derive(Kp)] -struct User { - name: String, - email: Option, -} - -// Usage -let name_kp = User::name_r(); // Returns KeyPaths -let email_kp = User::email_fr(); // Returns KeyPaths -``` - -#### After -```rust -use keypaths_proc::Kp; - -#[derive(Kp)] -struct User { - name: String, - email: Option, -} - -// Usage -let name_kp = User::name_r(); // Returns KeyPath -let email_kp = User::email_fr(); // Returns OptionalKeyPath -``` - -## Breaking Changes - -### 1. Type System -- **Before**: Single `KeyPaths` enum type -- **After**: Separate types: `KeyPath`, `OptionalKeyPath`, `WritableKeyPath`, `WritableOptionalKeyPath` - -### 2. Method Names -- `compose()` → `then()` for chaining -- `get()` on `KeyPath` returns `&Value` (not `Option<&Value>`) -- `get_mut()` on `WritableKeyPath` returns `&mut Value` (not `Option<&mut Value>`) - -### 3. Owned KeyPaths -- **Before**: `KeyPaths::owned()` and `KeyPaths::failable_owned()` supported -- **After**: Owned keypaths not supported (use readable/writable instead) - -### 4. Send + Sync Bounds -- **Before**: `KeyPaths` enum implements `Send + Sync` -- **After**: `rust-keypaths` types do not implement `Send + Sync` (by design for better performance) - -### 5. Type Parameters -- Container unwrapping methods (`for_box()`, `for_arc()`, `for_rc()`) no longer require explicit type parameters - types are automatically inferred - -## Migration Steps - -### Step 1: Update Dependencies -```toml -# Remove -key-paths-core = "1.7.0" -key-paths-derive = "1.1.0" - -# Add -rust-keypaths = "1.0.0" -keypaths-proc = "1.0.0" -``` - -### Step 2: Update Imports -```rust -// Before -use key_paths_core::KeyPaths; -use key_paths_derive::Keypaths; - -// After -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use keypaths_proc::Kp; -``` - -### Step 3: Update KeyPath Creation -```rust -// Before -let kp = KeyPaths::readable(|s: &Struct| &s.field); - -// After -let kp = KeyPath::new(|s: &Struct| &s.field); -``` - -### Step 4: Update Chaining -```rust -// Before -let chained = kp1.compose(kp2); - -// After -let chained = kp1.then(kp2); -``` - -### Step 5: Update Access Patterns -```rust -// Before - KeyPath returns Option -let kp = KeyPaths::readable(|s: &Struct| &s.field); -if let Some(value) = kp.get(&instance) { - // ... -} - -// After - KeyPath returns direct reference -let kp = KeyPath::new(|s: &Struct| &s.field); -let value = kp.get(&instance); // Direct access, no Option -``` - -### Step 6: Update Container Unwrapping -```rust -// Before -let unwrapped = kp.for_box::(); - -// After -let unwrapped = kp.for_box(); // Type inferred automatically -``` - -## Performance Improvements - -After migration, you can expect: - -- **Write Operations**: Can be 2-7% faster than manual unwrapping at deeper nesting levels -- **Read Operations**: ~2-3x overhead vs manual unwrapping, but absolute time is still sub-nanosecond -- **Better Inlining**: Compiler can optimize more aggressively -- **Zero Dynamic Dispatch**: No runtime overhead from trait objects - -See [BENCHMARK_REPORT.md](rust-keypaths/benches/BENCHMARK_REPORT.md) for detailed performance analysis. - -## Common Patterns - -### Pattern 1: Simple Field Access -```rust -// Before -let kp = KeyPaths::readable(|s: &User| &s.name); -let name = kp.get(&user).unwrap(); - -// After -let kp = KeyPath::new(|s: &User| &s.name); -let name = kp.get(&user); // No unwrap needed -``` - -### Pattern 2: Optional Field Access -```rust -// Before -let kp = KeyPaths::failable_readable(|s: &User| s.email.as_ref()); -if let Some(email) = kp.get(&user) { - // ... -} - -// After -let kp = OptionalKeyPath::new(|s: &User| s.email.as_ref()); -if let Some(email) = kp.get(&user) { - // ... -} -``` - -### Pattern 3: Deep Nesting -```rust -// Before -let kp1 = KeyPaths::failable_readable(|r: &Root| r.level1.as_ref()); -let kp2 = KeyPaths::failable_readable(|l1: &Level1| l1.level2.as_ref()); -let kp3 = KeyPaths::failable_readable(|l2: &Level2| l2.value.as_ref()); -let chained = kp1.compose(kp2).compose(kp3); - -// After -let kp1 = OptionalKeyPath::new(|r: &Root| r.level1.as_ref()); -let kp2 = OptionalKeyPath::new(|l1: &Level1| l1.level2.as_ref()); -let kp3 = OptionalKeyPath::new(|l2: &Level2| l2.value.as_ref()); -let chained = kp1.then(kp2).then(kp3); -``` - -### Pattern 4: Mutable Access -```rust -// Before -let mut user = User { name: "Akash".to_string() }; -let kp = KeyPaths::writable(|s: &mut User| &mut s.name); -if let Some(name_ref) = kp.get_mut(&mut user) { - *name_ref = "Bob".to_string(); -} - -// After -let mut user = User { name: "Akash".to_string() }; -let kp = WritableKeyPath::new(|s: &mut User| &mut s.name); -let name_ref = kp.get_mut(&mut user); // Direct access -*name_ref = "Bob".to_string(); -``` - -## Troubleshooting - -### Error: "trait bound `Send + Sync` is not satisfied" -**Solution**: If you need `Send + Sync`, stay with `key-paths-core` v1.6.0. `rust-keypaths` intentionally doesn't implement these traits for better performance. - -### Error: "method `compose` not found" -**Solution**: Use `then()` instead of `compose()`. - -### Error: "expected `Option`, found `&String`" -**Solution**: `KeyPath::get()` returns `&Value` directly, not `Option<&Value>`. Use `OptionalKeyPath` if you need `Option`. - -### Error: "type annotations needed" for `for_box()` -**Solution**: Remove the type parameter - `for_box()` now infers types automatically. - -## Need Help? - -- Check the [rust-keypaths README](rust-keypaths/README.md) for detailed API documentation -- See [examples](rust-keypaths/examples/) for comprehensive usage examples -- Review [benchmark results](rust-keypaths/benches/BENCHMARK_REPORT.md) for performance analysis - -## Summary - -Migrating from `key-paths-core` to `rust-keypaths` provides: -- ✅ Better performance (especially for write operations) -- ✅ Zero runtime overhead -- ✅ Better compiler optimizations -- ✅ Automatic type inference -- ⚠️ No `Send + Sync` support (by design) -- ⚠️ No owned keypaths (use readable/writable instead) - -For most use cases, `rust-keypaths` is the recommended choice. Only use `key-paths-core` v1.6.0 if you specifically need `Send + Sync` bounds or dynamic dispatch. - diff --git a/OPTIMIZATION_SUMMARY.md b/OPTIMIZATION_SUMMARY.md deleted file mode 100644 index 99e2deb..0000000 --- a/OPTIMIZATION_SUMMARY.md +++ /dev/null @@ -1,119 +0,0 @@ -# Performance Optimization Summary - -## Implemented Optimizations - -### Phase 1: Optimize Closure Composition ✅ - -**Problem**: The `and_then` closure in composition creates unnecessary overhead. - -**Solution**: Replaced `and_then` with direct `match` statements in all composition cases. - -**Changes Made**: -- Replaced `f1(r).and_then(|m| f2(m))` with direct `match` statements -- Applied to all failable keypath compositions: - - `FailableReadable` + `FailableReadable` - - `FailableWritable` + `FailableWritable` - - `FailableReadable` + `ReadableEnum` - - `ReadableEnum` + `FailableReadable` - - `WritableEnum` + `FailableReadable` - - `WritableEnum` + `FailableWritable` - - `ReadableEnum` + `ReadableEnum` - - `WritableEnum` + `ReadableEnum` - - `WritableEnum` + `WritableEnum` - - `FailableOwned` + `FailableOwned` - -**Expected Improvement**: 20-30% faster reads and writes - -**Code Pattern**: -```rust -// Before -FailableReadable(Arc::new(move |r| f1(r).and_then(|m| f2(m)))) - -// After -let f1 = f1.clone(); -let f2 = f2.clone(); -FailableReadable(Arc::new(move |r| { - match f1(r) { - Some(m) => f2(m), - None => None, - } -})) -``` - -### Phase 3: Inline Hints and Compiler Optimizations ✅ - -**Problem**: Compiler can't inline through dynamic dispatch. - -**Solution**: Added `#[inline(always)]` to hot paths. - -**Changes Made**: -- Added `#[inline(always)]` to `get()` method -- Added `#[inline(always)]` to `get_mut()` method -- Added `#[inline]` to `compose()` method - -**Expected Improvement**: 10-15% faster reads and writes - -**Code Changes**: -```rust -// Before -#[inline] -pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> { ... } - -// After -#[inline(always)] -pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> { ... } -``` - -## Not Implemented - -### Phase 2: Specialize for Common Cases - -**Reason**: This would require significant API changes and trait bounds that may not be feasible with the current architecture. The generic composition is already well-optimized with Phase 1 changes. - -### Phase 4: Reduce Arc Indirection - -**Reason**: This would require architectural changes to support both `Arc` and `Rc`, or function pointers. This is a larger change that would affect the entire API surface. - -## Expected Combined Improvements - -With Phase 1 and Phase 3 implemented: - -| Operation | Before | After Phase 1+3 | Expected Improvement | -|-----------|--------|-----------------|---------------------| -| **Read (3 levels)** | 944.68 ps | ~660-755 ps | 20-30% faster | -| **Write (3 levels)** | 5.04 ns | ~3.5-4.0 ns | 20-30% faster | -| **Deep Read** | 974.13 ps | ~680-780 ps | 20-30% faster | -| **Write Deep** | 10.71 ns | ~7.5-8.6 ns | 20-30% faster | - -**Combined Expected Improvement**: 30-45% faster (multiplicative effect of Phase 1 + Phase 3) - -## Testing - -To verify the improvements, run: - -```bash -cargo bench --bench keypath_vs_unwrap -``` - -Compare the results with the baseline benchmarks in `benches/BENCHMARK_RESULTS.md`. - -## Files Modified - -1. **`key-paths-core/src/lib.rs`**: - - Updated `compose()` method: Replaced all `and_then` calls with direct `match` statements - - Updated `get()` method: Added `#[inline(always)]` attribute - - Updated `get_mut()` method: Added `#[inline(always)]` attribute - -## Next Steps - -1. Run benchmarks to verify actual improvements -2. If Phase 2 is needed, consider adding specialized composition methods -3. If Phase 4 is needed, consider architectural changes for `Rc`/function pointer support - -## Notes - -- All changes maintain backward compatibility -- No API changes required -- Compilation verified ✅ -- All tests should pass (verify with `cargo test`) - diff --git a/PANIC_ON_CLONE_IMPLEMENTATION.md b/PANIC_ON_CLONE_IMPLEMENTATION.md deleted file mode 100644 index b8f132b..0000000 --- a/PANIC_ON_CLONE_IMPLEMENTATION.md +++ /dev/null @@ -1,166 +0,0 @@ -# Panic-On-Clone Tests - Implementation Summary - -## What Was Added - -Three definitive tests that **prove beyond doubt** that `LockKp` performs zero deep cloning during composition. - -## Test Files - -### Location -`src/lock.rs` (lines 1265+) - -### Test Count -- **3 new panic-on-clone tests** -- Total lock module tests: **15** -- Total library tests: **71** ✅ - -## The Tests - -### 1. `test_rwlock_panic_on_clone_proof` -- **Purpose**: Proves RwLock composition never deep clones -- **Structure**: 2-level nested RwLocks with `PanicOnClone` at each level -- **Key**: If `Level1`, `Level2`, or `PanicOnClone` is cloned → test panics -- **Result**: ✅ PASSES = No deep cloning - -### 2. `test_mutex_panic_on_clone_proof` -- **Purpose**: Proves Mutex composition never deep clones (even large data) -- **Structure**: 2-level nested Mutexes with 1MB `PanicOnClone` at each level -- **Key**: If `Mid`, `Inner`, or `PanicOnClone` (1MB) is cloned → test panics -- **Result**: ✅ PASSES = No 1MB copies made - -### 3. `test_mixed_locks_panic_on_clone_proof` -- **Purpose**: Proves mixed RwLock→Mutex composition never deep clones -- **Structure**: RwLock→Mutex chain with `NeverClone` (10KB) at each level -- **Key**: Calls `get()` twice to verify consistent shallow behavior -- **Result**: ✅ PASSES = No cloning on multiple accesses - -## The Proof Mechanism - -Each test uses structs with `Clone` implementations that **panic with error messages**: - -```rust -impl Clone for PanicOnClone { - fn clone(&self) -> Self { - panic!("❌ DEEP CLONE DETECTED! PanicOnClone was cloned!"); - } -} -``` - -This makes any deep cloning **immediately visible** as a test failure. - -## What's Proven - -### ✅ Guaranteed Behaviors -1. **Arc cloning is shallow**: Only refcount incremented, never inner value -2. **Composition is zero-copy**: `lock1.compose(lock2)` doesn't clone locked data -3. **Multiple access is safe**: Repeated `get()` calls don't accumulate clones -4. **Mixed types work**: RwLock + Mutex composition is shallow - -### ❌ What NEVER Happens -1. **No inner value cloning**: `Level1`, `Level2`, `Mid`, `Inner` never cloned -2. **No user data cloning**: `PanicOnClone`, `NeverClone` never cloned -3. **No memory duplication**: Large data (1MB, 10KB) never duplicated - -## Test Results - -```bash -$ cargo test --lib panic_on_clone - -running 3 tests -test lock::tests::test_mixed_locks_panic_on_clone_proof ... ok -test lock::tests::test_rwlock_panic_on_clone_proof ... ok -test lock::tests::test_mutex_panic_on_clone_proof ... ok - -test result: ok. 3 passed; 0 failed; 0 ignored -``` - -**All tests pass = Zero deep cloning confirmed!** ✅ - -If ANY deep cloning occurred: -``` -thread 'lock::tests::test_rwlock_panic_on_clone_proof' panicked at: -'❌ DEEP CLONE DETECTED! PanicOnClone was cloned! This should NEVER happen!' -``` - -But this **never happens** because all cloning is shallow. - -## Documentation - -### New Files Created -1. **`PANIC_ON_CLONE_TESTS.md`**: Detailed explanation of the panic-on-clone testing strategy -2. **Updated `LOCK_KP_GUIDE.md`**: Added panic-on-clone tests section - -### Key Sections -- Test strategy and proof mechanism -- What each test proves -- Technical memory layout analysis -- Comparison with other testing approaches -- Performance implications - -## Code Quality - -### Comments Added -Inline comments in tests explain: -- Why structs panic on clone -- What would cause test failure -- What passing proves about shallow cloning - -Example: -```rust -// CRITICAL TEST: Compose both locks -// If any deep cloning occurs, the PanicOnClone will trigger and test will fail -let composed = lock1.compose(lock2); - -// ✅ SUCCESS: No panic means no deep cloning occurred! -let value = composed.get(&root); -``` - -## Performance Implications - -Since tests prove no deep cloning: -- **Memory**: O(1) overhead per composition (only refcount) -- **CPU**: O(1) atomic operations per composition -- **Latency**: Microseconds (no large memory copies) -- **Scalability**: Composing N locks is O(N) refcount ops, not O(N) data copies - -Even with **1MB data** at each level, composition is **instant**. - -## Why This Matters - -### Traditional Approaches Are Insufficient -❌ Manual code inspection → Human error, assumptions -❌ Memory profiling → Noisy, requires external tools -❌ Arc refcount checks → Indirect, doesn't prove what happened - -### Panic-On-Clone Is Definitive -✅ **Direct**: Clone happens → test fails immediately -✅ **Explicit**: Clear panic message shows what was cloned -✅ **Simple**: No external tools needed -✅ **Mathematical**: Test passes ⇒ Proof of no deep cloning - -## Conclusion - -The three panic-on-clone tests provide **formal proof** that `LockKp` composition is truly zero-copy: - -1. Test passes ⇒ No panic -2. No panic ⇒ `clone()` never called -3. `clone()` not called ⇒ No deep cloning -4. No deep cloning ⇒ Only Arc refcounts incremented - -This is as close to a **mathematical proof** as possible in a test suite! - -## Files Modified - -1. **`src/lock.rs`**: Added 3 panic-on-clone tests (~380 lines) -2. **`PANIC_ON_CLONE_TESTS.md`**: Detailed analysis (new file) -3. **`LOCK_KP_GUIDE.md`**: Updated with test information - -## Test Coverage - -**All 71 tests pass**: -- 56 core library tests -- 7 Mutex lock tests -- 5 RwLock lock tests -- 3 panic-on-clone proof tests ⭐ - -Total runtime: **~1.5 seconds** ✅ diff --git a/PANIC_ON_CLONE_TESTS.md b/PANIC_ON_CLONE_TESTS.md deleted file mode 100644 index 3508ca1..0000000 --- a/PANIC_ON_CLONE_TESTS.md +++ /dev/null @@ -1,306 +0,0 @@ -# Panic-On-Clone Tests - Definitive Shallow Cloning Proof - -## Overview - -Three critical tests definitively prove that `LockKp` performs **ZERO deep cloning** during lock composition and access. These tests use structs that **panic when cloned**, making any deep cloning immediately detectable. - -## Test Strategy - -### The Proof Mechanism - -1. Create a struct with `Clone` impl that **panics with error message** -2. Nest these panic structs inside locked data structures -3. Compose multiple lock levels together -4. Perform `get()` operations -5. **If test passes = No deep cloning occurred** - -```rust -impl Clone for PanicOnClone { - fn clone(&self) -> Self { - panic!("❌ DEEP CLONE DETECTED! This should NEVER happen!"); - } -} -``` - -## The Four Tests - -### 1. `test_rwlock_panic_on_clone_proof` 🔒 - -**Target**: Proves RwLock composition is shallow. - -**Structure**: -``` -Root - └── Arc> - ├── PanicOnClone (panic_data) - └── Arc> - ├── PanicOnClone (panic_data2) - └── i32 (value) -``` - -**Operations**: -- Creates two-level RwLock nesting -- Composes both levels: `lock1.compose(lock2)` -- Calls `get()` to access inner value - -**What Would Fail**: -- If `Level1` is cloned → `panic!("Level1 was deeply cloned!")` -- If `Level2` is cloned → `panic!("Level2 was deeply cloned!")` -- If `PanicOnClone` is cloned → `panic!("PanicOnClone was cloned!")` - -**Result**: ✅ **PASSES** - No panics = No deep cloning! - -### 2. `test_mutex_panic_on_clone_proof` 🔒 - -**Target**: Proves Mutex composition is shallow, even with large data. - -**Structure**: -``` -Root - └── Arc> - ├── PanicOnClone (1 MB data) - └── Arc> - ├── PanicOnClone (1 MB data) - └── String (value) -``` - -**Special Feature**: Each `PanicOnClone` contains **1 MB of data** (`vec![0u8; 1_000_000]`) - -**Operations**: -- Creates two-level Mutex nesting with 1MB at each level -- Composes both levels -- Calls `get()` to access value - -**What Would Fail**: -- If `Mid` is cloned → `panic!("Mid was deeply cloned!")` -- If `Inner` is cloned → `panic!("Inner was deeply cloned!")` -- If `PanicOnClone` is cloned → Panic (would also copy 1MB!) - -**Result**: ✅ **PASSES** - No panics = No 1MB copies made! - -### 3. `test_mixed_locks_panic_on_clone_proof` 🔒 - -**Target**: Proves mixed RwLock→Mutex composition is shallow. - -**Structure**: -``` -Root - └── Arc> - ├── NeverClone (id=1, 10KB data) - └── Arc> - ├── NeverClone (id=2, 10KB data) - └── i32 (value) -``` - -**Special Feature**: -- Combines RwLock and Mutex -- Uses `NeverClone` struct with unique `id` field -- Each `NeverClone` has 10KB of data - -**Operations**: -- Creates RwLock → Mutex chain -- Composes both levels -- Calls `get()` **twice** to verify consistent behavior - -**What Would Fail**: -- If `Mid` is cloned → `panic!("Mid was deeply cloned!")` -- If `Inner` is cloned → `panic!("Inner was deeply cloned!")` -- If `NeverClone` is cloned → `panic!("NeverClone with id N was cloned!")` - -**Result**: ✅ **PASSES** - No panics on multiple gets! - -### 4. `test_rc_refcell_panic_on_clone_proof` 🔒 - -**Target**: Proves Rc composition is shallow (single-threaded). - -**Structure**: -``` -Root - └── Rc> - ├── PanicOnClone (panic_data) - └── Rc> - ├── PanicOnClone (panic_data2) - └── i32 (value) -``` - -**Operations**: -- Creates two-level Rc nesting -- Composes both levels: `lock1.compose(lock2)` -- Calls `get()` twice to verify consistent behavior - -**What Would Fail**: -- If `Level1` is cloned → `panic!("Level1 was deeply cloned in Rc!")` -- If `Level2` is cloned → `panic!("Level2 was deeply cloned in Rc!")` -- If `PanicOnClone` is cloned → `panic!("PanicOnClone was cloned in Rc!")` - -**Result**: ✅ **PASSES** - No panics = Rc shallow cloning works! - -## What These Tests Prove - -### ✅ Guaranteed Shallow Cloning - -1. **Arc Cloning is Shallow**: Only the refcount is incremented, never the inner value -2. **Rc Cloning is Shallow**: Only the refcount is incremented (single-threaded), never the inner value -3. **LockKp Composition is Zero-Copy**: Composing locks doesn't clone locked data -4. **Multiple Access is Safe**: Repeated `get()` calls don't accumulate clones -5. **Mixed Lock Types Work**: RwLock and Mutex can be composed without deep cloning - -### ❌ What NEVER Happens - -1. **No Inner Value Cloning**: The locked data (`Level1`, `Level2`, `Mid`, `Inner`) is never cloned -2. **No User Data Cloning**: The `PanicOnClone`/`NeverClone` structs are never cloned -3. **No Memory Duplication**: Large data (1MB, 10KB) is never duplicated - -## Technical Details - -### Memory Layout (Conceptual) - -**Before Composition**: -``` -lock1: LockKp { prev, mid, next } @ 0x1000 -lock2: LockKp { prev, mid, next } @ 0x2000 -Arc> → Heap @ 0x3000 (refcount: 1) -Arc> → Heap @ 0x4000 (refcount: 1) -``` - -**After `lock1.compose(lock2)`**: -``` -composed: LockKp { prev, mid, next } @ 0x5000 -Arc> → Heap @ 0x3000 (refcount: 2) ← Only refcount changed! -Arc> → Heap @ 0x4000 (refcount: 2) ← Only refcount changed! - -Data at 0x3000 and 0x4000 was NEVER copied! -``` - -### Cloning Breakdown - -When `.compose()` is called: - -1. **`prev` keypath**: Function pointers copied (no heap allocation) -2. **`mid` accessor**: `PhantomData` copied (zero-sized, zero-cost) -3. **`next` keypath**: Function pointers copied (no heap allocation) -4. **Arc containers**: Refcount incremented (one atomic operation) -5. **Inner data**: **NEVER TOUCHED** ✅ - -## Running the Tests - -```bash -# Run all panic-on-clone tests -cargo test --lib panic_on_clone - -# Output: -# test lock::tests::test_rwlock_panic_on_clone_proof ... ok -# test lock::tests::test_mutex_panic_on_clone_proof ... ok -# test lock::tests::test_mixed_locks_panic_on_clone_proof ... ok -# test lock::tests::test_rc_refcell_panic_on_clone_proof ... ok -``` - -If ANY deep cloning occurred, you would see: -``` -thread 'lock::tests::test_rwlock_panic_on_clone_proof' panicked at 'PanicOnClone was cloned!' -``` - -But all tests **pass silently** = Zero deep cloning! ✅ - -## Code Excerpts - -### PanicOnClone Definition - -```rust -/// This struct PANICS if cloned - proving no deep cloning occurs -struct PanicOnClone { - data: String, -} - -impl Clone for PanicOnClone { - fn clone(&self) -> Self { - panic!("❌ DEEP CLONE DETECTED! PanicOnClone was cloned! This should NEVER happen!"); - } -} -``` - -### NeverClone with ID - -```rust -/// Panic-on-clone struct for verification -struct NeverClone { - id: usize, - large_data: Vec, -} - -impl Clone for NeverClone { - fn clone(&self) -> Self { - panic!("❌ NeverClone with id {} was cloned!", self.id); - } -} -``` - -### Critical Test Section - -```rust -// CRITICAL TEST: Compose both locks -// If any deep cloning occurs, the PanicOnClone will trigger and test will fail -let composed = lock1.compose(lock2); - -// If we get here without panic, shallow cloning is working correctly! -// Now actually use the composed keypath -let value = composed.get(&root); - -// ✅ SUCCESS: No panic means no deep cloning occurred! -assert!(value.is_some()); -``` - -## Comparison with Other Approaches - -### Traditional Testing Approaches - -❌ **Manual Inspection**: "Looking at the code, it seems like it doesn't clone" -- Problem: Human error, assumptions, complex generic code - -❌ **Memory Profiling**: Tracking heap allocations -- Problem: Noisy, hard to isolate, requires external tools - -❌ **Ref-counting Checks**: Verifying Arc strong_count -- Problem: Indirect, doesn't prove what happened - -✅ **Panic-on-Clone Tests**: Struct panics if cloned -- **Direct**: If clone happens, test fails immediately -- **Explicit**: Clear panic message shows exactly what was cloned -- **Simple**: No external tools needed -- **Definitive**: Pass = Proof of no deep cloning - -## Conclusion - -The four panic-on-clone tests provide **mathematical proof** that `LockKp` composition performs only shallow cloning: - -1. **Test passes** ⇒ No panic occurred -2. **No panic** ⇒ `PanicOnClone.clone()` was never called -3. **`clone()` not called** ⇒ No deep cloning occurred -4. **No deep cloning** ⇒ Only Arc/Rc refcounts incremented (shallow) - -This is as close to a formal proof as you can get in a test suite! - -The tests cover: -- **Multi-threaded**: Arc, Arc -- **Single-threaded**: Rc -- **Mixed compositions**: RwLock→Mutex -- **Multiple access patterns**: Repeated `get()` calls - -All prove the same thing: **Zero deep cloning, always!** ✅ - -## Performance Implications - -Since no deep cloning occurs: - -- **Memory**: O(1) overhead per composition (just refcount increment) -- **CPU**: O(1) atomic operations per composition -- **Latency**: Microseconds, not milliseconds (no large memory copies) -- **Scalability**: Composing N locks is still O(N) refcount increments, not O(N) data copies - -Even with 1MB of data at each level, composition is **instant** because the data is never touched. - -## Further Reading - -- `SHALLOW_CLONING.md`: Detailed analysis of shallow cloning guarantees -- `LOCK_KP_GUIDE.md`: Complete usage guide for LockKp -- `src/lock.rs`: Full implementation with inline shallow cloning comments diff --git a/PROC_MACRO_LOCK_SUPPORT.md b/PROC_MACRO_LOCK_SUPPORT.md deleted file mode 100644 index 81afe9f..0000000 --- a/PROC_MACRO_LOCK_SUPPORT.md +++ /dev/null @@ -1,144 +0,0 @@ -# Proc Macro Lock Support - -## ⚠️ IMPORTANT: Lock Type Defaults - -**`Mutex` and `RwLock` default to `parking_lot` types!** - -- If you write `Arc>` or `Arc>`, it will be treated as **parking_lot** -- To use **std::sync**, you MUST use the full path: `Arc>` or `Arc>` - -```rust -use parking_lot::RwLock; // OK - will use parking_lot methods -use std::sync::RwLock; // Must use: std::sync::RwLock in the field type - -#[derive(Kp)] -struct Example { - // These default to parking_lot: - data1: Arc>, // parking_lot (default) - data2: Arc>, // parking_lot (default) - - // These use std::sync: - data3: Arc>, // std::sync (explicit) - data4: Arc>, // std::sync (explicit) -} -``` - -## Generated Methods - -### For parking_lot (Default) - -For fields like `f1: Arc>` (without `std::sync::` prefix): - -| Method | Returns | Description | -|--------|---------|-------------| -| `{field}_r()` | `KeyPath>>` | Readable keypath to the lock field | -| `{field}_w()` | `WritableKeyPath>>` | Writable keypath to the lock field | -| `{field}_fr_at(inner_kp)` | Chain | Chains through parking_lot lock for reading | -| `{field}_fw_at(inner_kp)` | Chain | Chains through parking_lot lock for writing | - -### For std::sync (Explicit) - -For fields like `f1: Arc>`: - -| Method | Returns | Description | -|--------|---------|-------------| -| `{field}_r()` | `KeyPath>>` | Readable keypath to the lock field | -| `{field}_w()` | `WritableKeyPath>>` | Writable keypath to the lock field | -| `{field}_fr_at(inner_kp)` | Chain | Chains through std::sync lock for reading | -| `{field}_fw_at(inner_kp)` | Chain | Chains through std::sync lock for writing | - -## Usage Examples - -### parking_lot (Default) - -```rust -use std::sync::Arc; -use parking_lot::RwLock; -use keypaths_proc::Kp; - -#[derive(Kp)] -#[All] -struct Container { - data: Arc>, // parking_lot (default) -} - -#[derive(Kp)] -#[All] -struct Inner { - value: String, -} - -fn main() { - let instance = Container { - data: Arc::new(RwLock::new(Inner { value: "hello".into() })), - }; - - // Use the generated _fr_at() method (parking_lot) - Container::data_fr_at(Inner::value_r()) - .get(&instance, |value| { - println!("Value: {}", value); - }); - - // Or chain manually - Container::data_r() - .chain_arc_parking_rwlock_at_kp(Inner::value_r()) - .get(&instance, |value| { - println!("Value: {}", value); - }); -} -``` - -### std::sync (Explicit) - -```rust -use std::sync::{Arc, RwLock}; -use keypaths_proc::Kp; - -#[derive(Kp)] -#[All] -struct Container { - // MUST use full path for std::sync - data: Arc>, -} - -#[derive(Kp)] -#[All] -struct Inner { - value: String, -} - -fn main() { - let instance = Container { - data: Arc::new(RwLock::new(Inner { value: "hello".into() })), - }; - - // Use the generated _fr_at() method (std::sync) - Container::data_fr_at(Inner::value_r()) - .get(&instance, |value| { - println!("Value: {}", value); - }); - - // Or chain manually - Container::data_r() - .chain_arc_rwlock_at_kp(Inner::value_r()) - .get(&instance, |value| { - println!("Value: {}", value); - }); -} -``` - -## Why This Design? - -1. **parking_lot is faster** - No lock poisoning, better performance -2. **Simpler common case** - Most users will use parking_lot -3. **Explicit std::sync** - Forces awareness when using std::sync -4. **Compile-time safety** - The macro detects `std::sync::` in the path and generates appropriate code - -## Testing - -Run the parking_lot example: -```bash -cargo run --example parking_lot_nested_chain --features parking_lot -``` - -This demonstrates chaining through multiple nested `Arc>` layers with zero-copy access. diff --git a/RC_REFCELL_IMPLEMENTATION.md b/RC_REFCELL_IMPLEMENTATION.md deleted file mode 100644 index 3feb864..0000000 --- a/RC_REFCELL_IMPLEMENTATION.md +++ /dev/null @@ -1,286 +0,0 @@ -# RcRefCellAccess Implementation Summary - -## Overview - -Added `RcRefCellAccess` for `Rc>` - the single-threaded equivalent of `Arc>`. This provides interior mutability with runtime borrow checking in single-threaded contexts without the overhead of atomic operations. - -## What Was Added - -### 1. `RcRefCellAccess` Struct - -**Location**: `src/lock.rs` (lines 560-653) - -**Purpose**: Lock access implementation for `Rc>` - -```rust -#[derive(Clone)] // ZERO-COST: Only clones PhantomData (zero-sized type) -pub struct RcRefCellAccess { - _phantom: std::marker::PhantomData, -} -``` - -### 2. LockAccess Implementations - -Two trait implementations for immutable and mutable access: - -**Immutable Access** (`&T`): -- Uses `RefCell::borrow()` for shared access -- Allows multiple concurrent borrows -- Returns `Option<&'a T>` - -**Mutable Access** (`&mut T`): -- Uses `RefCell::borrow_mut()` for exclusive access -- Only one mutable borrow at a time -- Returns `Option<&'a mut T>` - -### 3. Export in lib.rs - -```rust -pub use lock::{LockKp, LockAccess, ArcMutexAccess, ArcRwLockAccess, RcRefCellAccess, LockKpType}; -``` - -## Tests Added - -### 5 Comprehensive Tests - -1. **`test_rc_refcell_basic`** - - Basic Rc functionality - - Tests `get()` and `set()` operations - - Verifies value retrieval and mutation - -2. **`test_rc_refcell_compose_two_levels`** - - Two-level Rc composition - - Tests `compose()` method - - Verifies nested lock navigation - -3. **`test_rc_refcell_three_levels`** - - Three-level deep Rc composition - - Tests multiple `compose()` calls - - Proves deep nesting works correctly - -4. **`test_rc_refcell_panic_on_clone_proof`** ⭐ CRITICAL - - Uses `PanicOnClone` struct that panics if cloned - - Composes two Rc levels - - Calls `get()` multiple times - - **Passing proves zero deep cloning!** - -5. **`test_rc_refcell_vs_arc_mutex`** - - API comparison between Rc and Arc - - Demonstrates identical usage patterns - - Shows single-threaded vs multi-threaded equivalence - -## Key Features - -### Semantics - -- **Runtime Borrow Checking**: RefCell checks borrows at runtime (panics on violation) -- **Multiple Readers**: Multiple immutable borrows allowed simultaneously -- **Exclusive Writer**: Only one mutable borrow at a time -- **NOT Thread-Safe**: Use only in single-threaded contexts - -### Performance Characteristics - -- **No Atomic Operations**: Unlike Arc, Rc uses simple reference counting -- **Lower Overhead**: No thread synchronization needed -- **Very Low Cost**: Faster than Arc in single-threaded code -- **Zero-Cost Clone**: PhantomData only, compiled away - -### Cloning Behavior - -**SHALLOW CLONING GUARANTEED:** -1. `RcRefCellAccess::clone()` - Only clones PhantomData (zero-sized) -2. `Rc::clone()` - Only increments refcount (no atomic ops) -3. Inner data is **NEVER** cloned - only refcount changes -4. Proven by `test_rc_refcell_panic_on_clone_proof` - -## Comparison Table - -| Feature | Arc | Arc | Rc | -|---------|------------|-------------|-------------| -| **Thread-safe** | ✅ Yes | ✅ Yes | ❌ No | -| **Multiple readers** | ❌ Blocked | ✅ Concurrent | ✅ Concurrent | -| **Write access** | ✅ Exclusive | ✅ Exclusive | ✅ Exclusive | -| **Atomic ops** | Yes | Yes | No | -| **Overhead** | Low | Moderate | Very Low | -| **Borrow check** | Compile-time | Compile-time | Runtime | -| **Panic on violation** | No (deadlock) | No (deadlock) | Yes (panic) | -| **Best for** | Multi-threaded | Read-heavy, threaded | Single-threaded | -| **Use when** | Need thread safety | Many concurrent readers | No threads | - -## Use Cases - -### When to Use Rc - -✅ **Good for:** -- Single-threaded applications -- UI frameworks (single event loop) -- Web assembly (single-threaded) -- Game engines (single-threaded logic) -- Parsers and compilers (single-threaded phases) -- Lower overhead than Arc/Mutex - -❌ **Avoid for:** -- Multi-threaded applications -- Shared state across threads -- Parallel processing -- Server applications with multiple threads - -### Example Use Cases - -```rust -// Single-threaded UI application -struct App { - state: Rc>, -} - -// Game engine (single-threaded) -struct GameWorld { - entities: Rc>>, -} - -// Parser with mutable state -struct Parser { - symbol_table: Rc>>, -} -``` - -## Documentation Updates - -### 1. LOCK_KP_GUIDE.md -- Added `RcRefCellAccess` section with examples -- Updated comparison table to include Rc -- Added 5 new tests to test section -- Updated total test count to 76 - -### 2. PANIC_ON_CLONE_TESTS.md -- Added `test_rc_refcell_panic_on_clone_proof` as 4th test -- Updated conclusion to cover single-threaded case -- Added Rc to shallow cloning guarantees - -### 3. This Document (RC_REFCELL_IMPLEMENTATION.md) -- Comprehensive implementation summary -- Use cases and best practices -- Comparison with Arc/Mutex variants - -## Test Results - -```bash -$ cargo test --lib rc_refcell - -running 5 tests -test lock::tests::test_rc_refcell_panic_on_clone_proof ... ok -test lock::tests::test_rc_refcell_three_levels ... ok -test lock::tests::test_rc_refcell_vs_arc_mutex ... ok -test lock::tests::test_rc_refcell_compose_two_levels ... ok -test lock::tests::test_rc_refcell_basic ... ok - -test result: ok. 5 passed; 0 failed; 0 ignored -``` - -```bash -$ cargo test --lib - -test result: ok. 76 passed; 0 failed; 0 ignored -``` - -**All 76 tests pass!** (56 core + 7 Mutex + 5 RwLock + 3 panic-on-clone + 5 Rc) - -## Code Quality - -### Comments and Documentation - -- Comprehensive doc comments on struct and methods -- Clear explanation of RefCell semantics -- Shallow cloning comments throughout -- Performance characteristics documented -- Thread-safety warnings included - -### Type Safety - -- Proper lifetime management with `'a` -- PhantomData for zero-sized type safety -- Borrow trait constraints enforced -- Generic over inner type `T` - -### Integration - -- Seamlessly works with existing `LockKp` infrastructure -- Same `compose()` and `then()` API -- Compatible with all existing HOFs -- Can be mixed with Arc and Arc in different parts of code - -## Benefits - -1. **Lower Overhead**: No atomic operations in single-threaded code -2. **Familiar API**: Identical to Arc usage -3. **Composable**: Works with `compose()` for multi-level locks -4. **Zero-Copy**: Shallow cloning proven by panic tests -5. **Type-Safe**: Full type checking at compile time -6. **Ergonomic**: Simple `LockKp::new()` construction - -## Performance Implications - -### Memory -- **PhantomData**: Zero-sized, no runtime memory -- **Rc**: 8 bytes (pointer) + refcount overhead -- **RefCell**: Small runtime overhead for borrow tracking -- **Total**: ~16-24 bytes per Rc (vs ~24-32 for Arc) - -### CPU -- **Rc::clone()**: Simple integer increment (no atomic) -- **borrow()**: Check borrow count, increment -- **borrow_mut()**: Check no borrows, set flag -- **~2-5x faster** than Arc in single-threaded benchmarks - -### Latency -- **No contention**: No lock waiting in single-threaded code -- **No atomic ops**: No memory barriers or cache coherency overhead -- **Predictable**: No variable latency from thread scheduling - -## Migration Guide - -### From Arc to Rc - -```rust -// Before (multi-threaded) -use std::sync::{Arc, Mutex}; - -struct Root { - data: Arc>, -} - -let lock_kp = LockKp::new(prev, ArcMutexAccess::new(), next); - -// After (single-threaded) -use std::rc::Rc; -use std::cell::RefCell; - -struct Root { - data: Rc>, // Changed: Arc -> Rc, Mutex -> RefCell -} - -let lock_kp = LockKp::new(prev, RcRefCellAccess::new(), next); // Changed accessor -``` - -**That's it!** The API is identical. - -## Future Enhancements - -Possible future additions: -- `RcRefCellAccess::try_*` methods for graceful borrow failure -- `Cell` support for `Copy` types (even simpler than RefCell) -- Borrowing statistics/debugging helpers -- Integration with async single-threaded runtimes - -## Conclusion - -`RcRefCellAccess` provides a complete, tested, and documented implementation for single-threaded interior mutability within the `LockKp` framework. It offers: - -✅ **Full feature parity** with Arc and Arc -✅ **Lower overhead** for single-threaded use cases -✅ **Proven shallow cloning** via panic tests -✅ **Comprehensive test coverage** (5 new tests) -✅ **Complete documentation** updates -✅ **Type-safe and ergonomic** API - -The implementation is production-ready and follows all established patterns in the codebase. diff --git a/REFERENCE_SUPPORT.md b/REFERENCE_SUPPORT.md deleted file mode 100644 index 1cd353b..0000000 --- a/REFERENCE_SUPPORT.md +++ /dev/null @@ -1,334 +0,0 @@ -# Reference Support in rust-key-paths - -This document explains how to use keypaths with collections of references (`Vec<&T>`, `HashMap`, etc.) instead of owned values. - -## Problem Statement - -When working with collections of references (e.g., `Vec<&Product>` from `HashMap::values()`), the standard keypath methods like `.get()` expect the owned type `T`, not `&T`. This created a type mismatch: - -```rust -let products: Vec<&Product> = hashmap.values().collect(); -let name_path = Product::name_r(); // Returns KeyPaths - -// This doesn't work! product_ref is &&Product but get() expects &Product -for product_ref in &products { - name_path.get(product_ref); // Type mismatch! -} -``` - -## Solution: `get_ref()` and `get_mut_ref()` - -The `key-paths-core` library now provides two new methods that work with reference types: - -### `get_ref()` - -For immutable access when you have a reference to a reference: - -```rust -pub fn get_ref<'a, 'b>(&'a self, root: &'b &Root) -> Option<&'b Value> -where - 'a: 'b -``` - -**Usage:** -```rust -let products: Vec<&Product> = hashmap.values().collect(); -let name_path = Product::name_r(); - -for product_ref in &products { // product_ref is &&Product - if let Some(name) = name_path.get_ref(product_ref) { - println!("Product: {}", name); - } -} -``` - -### `get_mut_ref()` - -For mutable access when you have a mutable reference to a mutable reference: - -```rust -pub fn get_mut_ref<'a, 'b>(&'a self, root: &'b mut &mut Root) -> Option<&'b mut Value> -where - 'a: 'b -``` - -**Usage:** -```rust -let mut products_mut: Vec<&mut Product> = vec![]; -let name_path = Product::name_w(); - -for product_ref in &mut products_mut { // product_ref is &&mut Product - if let Some(name) = name_path.get_mut_ref(product_ref) { - *name = format!("New {}", name); - } -} -``` - -## Common Use Cases - -### 1. Working with HashMap Values - -```rust -use std::collections::HashMap; - -let map: HashMap = /* ... */; - -// Collect references without cloning -let products: Vec<&Product> = map.values().collect(); -let price_path = Product::price_r(); - -// Filter using keypaths -let affordable: Vec<&&Product> = products - .iter() - .filter(|p| price_path.get_ref(p).map_or(false, |&price| price < 100.0)) - .collect(); -``` - -### 2. Avoiding Unnecessary Cloning - -**❌ Without `get_ref` (requires cloning):** -```rust -let expensive: Vec = products - .iter() - .filter(|p| p.price > 100.0) - .cloned() // Creates copies! - .collect(); -``` - -**✓ With `get_ref` (no cloning):** -```rust -let expensive: Vec<&Product> = products - .iter() - .filter(|p| price_path.get_ref(p).map_or(false, |&price| price > 100.0)) - .collect(); -``` - -### 3. Lock-Aware Patterns with Arc> - -```rust -use std::sync::Arc; -use parking_lot::RwLock; - -let shared: Arc>> = /* ... */; - -// Read lock and collect references -let products: Vec<&Product> = shared.read().values().collect(); - -// Query without cloning the data -let results: Vec<&&Product> = products - .iter() - .filter(|p| category_path.get_ref(p).map_or(false, |c| c == "Electronics")) - .collect(); -``` - -### 4. Grouped Data Processing - -```rust -let mut by_category: HashMap> = HashMap::new(); - -for product_ref in &products { - if let Some(category) = category_path.get_ref(product_ref) { - by_category - .entry(category.clone()) - .or_insert_with(Vec::new) - .push(*product_ref); - } -} -``` - -## Key Differences - -| Scenario | Method to Use | Example | -|----------|---------------|---------| -| Owned data: `&Product` | `.get()` | `name_path.get(&product)` | -| Reference: `&&Product` | `.get_ref()` | `name_path.get_ref(&product_ref)` | -| Mutable owned: `&mut Product` | `.get_mut()` | `name_path.get_mut(&mut product)` | -| Mutable ref: `&&mut Product` | `.get_mut_ref()` | `name_path.get_mut_ref(&mut product_ref)` | - -## Examples - -### Complete Working Example - -See [`examples/reference_keypaths.rs`](examples/reference_keypaths.rs) for a comprehensive demonstration including: - -- Working with `Vec<&T>` -- HashMap reference values -- Filtering without cloning -- Nested references -- Performance comparisons - -Run with: -```bash -cargo run --example reference_keypaths -``` - -### Test Suite - -See [`key-paths-core/examples/reference_test.rs`](key-paths-core/examples/reference_test.rs) for detailed test cases covering: - -- Basic `get_ref` functionality -- Nested references -- Failable keypaths with references -- Mutable reference access -- Value correctness verification - -Run with: -```bash -cd key-paths-core && cargo run --example reference_test -``` - -## Performance Benefits - -Using `get_ref()` with reference collections provides: - -1. **Zero-cost Abstraction**: Compiles to direct field access -2. **No Cloning**: Work with borrowed data directly -3. **Memory Efficiency**: Avoid duplicate allocations -4. **Type Safety**: Compiler-verified reference handling - -### Benchmark Example - -```rust -// Without get_ref: O(n) clones -let filtered: Vec = products - .iter() - .filter(|p| p.price < 100.0) - .cloned() // Allocates n new objects - .collect(); - -// With get_ref: Zero clones -let filtered: Vec<&Product> = products - .iter() - .filter(|p| price_path.get_ref(p).map_or(false, |&p| p < 100.0)) - .collect(); // Only allocates Vec of pointers -``` - -## Lifetime Semantics - -The lifetime bound `'a: 'b` in `get_ref` means: -- `'a`: Lifetime of the keypath itself -- `'b`: Lifetime of the data being accessed -- Constraint: The keypath must live at least as long as the borrowed data - -This ensures: -- No dangling references -- Safe access patterns -- Correct borrow checker validation - -## Integration with Query Builders - -The reference support integrates seamlessly with query builders: - -```rust -use rust_queries_core::*; - -let products: Vec<&Product> = hashmap.values().collect(); - -// Query builder with references -let results = products.lazy_query() - .where_(|p| price_path.get_ref(&p).map_or(false, |&price| price < 100.0)) - .order_by(|p| category_path.get_ref(&p)) - .collect(); -``` - -## Migration Guide - -If you have existing code using cloning, here's how to migrate: - -### Before (with cloning) -```rust -let data: Vec = hashmap.values().cloned().collect(); -data.lazy_query() - .where_(SomeStruct::field_r(), |x| /* ... */) - .collect() -``` - -### After (with references) -```rust -let data: Vec<&SomeStruct> = hashmap.values().collect(); -data.iter() - .filter(|item| { - SomeStruct::field_r().get_ref(item).map_or(false, |val| /* ... */) - }) - .collect() -``` - -## Best Practices - -1. **Use `get_ref()` when working with borrowed data** - - HashMap values - - Shared state (Arc/RwLock) - - Temporary collections - -2. **Use `get()` with owned data** - - Direct struct instances - - Vec (owned) - - Local variables - -3. **Prefer references when possible** - - Reduces memory allocation - - Better cache locality - - Faster for large structs - -4. **Consider ownership needs** - - Need to modify? Use owned or mutable references - - Read-only queries? Use immutable references - - Need to return data? Consider owned types - -## Troubleshooting - -### Error: "expected `T`, found `&T`" - -**Problem:** Mixing `get()` with reference types - -**Solution:** Use `get_ref()` instead: -```rust -// ❌ Wrong -name_path.get(&&product) - -// ✓ Correct -name_path.get_ref(&product_ref) -``` - -### Error: "lifetime may not live long enough" - -**Problem:** Keypaths lifetime doesn't outlive the data - -**Solution:** Ensure keypath is created before or with the same scope as data: -```rust -let name_path = Product::name_r(); // Create first -let products: Vec<&Product> = map.values().collect(); -// Now use with get_ref -``` - -### Error: "cannot borrow as mutable" - -**Problem:** Using `get_mut_ref()` with immutable references - -**Solution:** Use `get_ref()` for immutable access or ensure you have mutable references: -```rust -// For reading only -name_path.get_ref(&product_ref) - -// For writing (need &mut) -let mut product_mut_ref = &mut product; -name_path.get_mut_ref(&mut product_mut_ref) -``` - -## Future Enhancements - -Potential future additions: - -1. **Automatic deref coercion** for common patterns -2. **Iterator adapters** for seamless reference handling -3. **Query builder integration** with automatic reference detection -4. **Macro support** for generating reference-aware methods - -## Contributing - -Found a bug or have a suggestion? Please file an issue or submit a PR! - -## License - -Same as rust-key-paths: MPL-2.0 - diff --git a/SHALLOW_CLONING.md b/SHALLOW_CLONING.md deleted file mode 100644 index 0e1446f..0000000 --- a/SHALLOW_CLONING.md +++ /dev/null @@ -1,290 +0,0 @@ -# Shallow Cloning Guarantee - Lock Module - -## Overview - -This document provides a comprehensive explanation of why **ALL cloning operations in the `lock` module are SHALLOW (reference-counted) clones** with no deep data copying. - -## What is Shallow Cloning? - -**Shallow cloning** means copying pointers/references, not the actual data: -- **Deep clone**: Copies the entire data structure recursively (expensive) -- **Shallow clone**: Copies only pointers/references (cheap, constant-time) - -For reference-counted types like `Arc`, "cloning" just increments a counter. - -## Three Types of Cloning in Lock Module - -### 1. LockKp Structure Clone - -```rust -#[derive(Clone)] // SHALLOW: Clones function pointers and PhantomData only -pub struct LockKp<...> { - pub prev: Kp<...>, // Function pointers (cheap to copy) - pub mid: L, // Typically PhantomData (zero-sized) - pub next: Kp<...>, // Function pointers (cheap to copy) -} -``` - -**What gets cloned:** -- `prev`: Function pointers for getter/setter (8-16 bytes on 64-bit) -- `mid`: Typically `ArcMutexAccess` which is just `PhantomData` (0 bytes) -- `next`: Function pointers for getter/setter (8-16 bytes on 64-bit) - -**Cost**: O(1) - copying a few pointers -**Data cloned**: NONE - only function pointers - -### 2. Lock Container Clone (Arc>) - -```rust -where - Lock: Clone, // SHALLOW: Arc clone = reference count increment only -``` - -**For `Arc>`:** - -```rust -// When you clone Arc>: -let arc1 = Arc::new(Mutex::new(expensive_data)); -let arc2 = arc1.clone(); // ← SHALLOW: Only increments refcount - -// What actually happens: -// 1. Load current refcount (atomic operation) -// 2. Increment by 1 (atomic operation) -// 3. Return new Arc pointing to SAME data - -// expensive_data is NOT copied! -``` - -**Visual representation:** -``` -Before clone: -arc1 → [RefCount: 1] → [Mutex] → [ExpensiveData] - -After clone: -arc1 → [RefCount: 2] ← arc2 - ↓ - [Mutex] → [ExpensiveData] ← Same data, not copied! -``` - -**Cost**: O(1) - one atomic increment -**Data cloned**: NONE - just increments a counter - -### 3. Lock Accessor Clone (ArcMutexAccess) - -```rust -#[derive(Clone)] // ZERO-COST: Only clones PhantomData (zero-sized type) -pub struct ArcMutexAccess { - _phantom: std::marker::PhantomData, // Zero-sized, no runtime cost -} -``` - -**What is PhantomData?** -- A zero-sized type used for type-level information -- Exists only at compile-time -- Takes up 0 bytes at runtime -- Cloning it is a no-op (compiled away completely) - -**Cost**: O(1) - actually O(0), compiled to nothing -**Data cloned**: NONE - zero-sized type - -## Where Cloning Occurs - -### In `get()` method: - -```rust -pub fn get(&self, root: Root) -> Option -where - Lock: Clone, // SHALLOW: Arc clone = refcount increment only - V: Clone, -{ - // No explicit clone here, but Lock: Clone bound allows it - (self.prev.get)(root).and_then(|lock_value| { - let lock: &Lock = lock_value.borrow(); - // Arc is borrowed, not cloned in this path - self.mid.lock_read(lock)... - }) -} -``` - -### In `set()` method: - -```rust -let lock: &Lock = lock_value.borrow(); -// SHALLOW CLONE: For Arc>, this only increments the reference count -// The actual data T inside the Mutex is NOT cloned -let mut lock_clone = lock.clone(); // ← Arc refcount: 1 → 2 -``` - -**Why clone here?** -- Rust's borrow checker requires it to move into the closure -- Arc makes this cheap - just incrementing a counter -- The actual locked data is never copied - -### In `compose()` method: - -```rust -// SHALLOW CLONE #1: Clone the lock accessor (PhantomData) -let other_mid1 = other.mid.clone(); // ← Zero-cost -let other_mid2 = other.mid; - -// Later in the closure: -// SHALLOW CLONE #2: Clone the Arc> -let mut lock2_clone = lock2.clone(); // ← Refcount increment only -``` - -## Performance Analysis - -### Single-Level Lock - -```rust -let lock_kp = LockKp::new(prev, ArcMutexAccess::new(), next); -let value = lock_kp.get(&root); -``` - -**Cost breakdown:** -- Creating LockKp: 0 (just moves) -- Getting value: 0 Arc clones (only borrows) -- Total data cloned: **ZERO** - -### Two-Level Composition - -```rust -let composed = lock_kp1.compose(lock_kp2); -``` - -**Cost breakdown:** -- Clone `other.mid`: 0 bytes (PhantomData) -- In getter closure: 0 Arc clones (only borrows) -- In setter closure: 1 Arc clone = 1 atomic increment -- Total data cloned: **ZERO** (just a refcount increment) - -### Three-Level Composition - -```rust -let composed_all = lock_kp1.compose(lock_kp2).compose(lock_kp3); -``` - -**Cost breakdown:** -- First compose: 1 atomic increment (in setter path) -- Second compose: 1 atomic increment (in setter path) -- Total data cloned: **ZERO** (just 2 atomic increments) - -## Memory Footprint - -### LockKp Size - -```rust -use std::mem::size_of; - -// Approximate sizes on 64-bit system: -size_of::() == 8; // Function pointer -size_of::>() == 0; // PhantomData -size_of::>() ≈ 48-64; // Function pointers + PhantomData -``` - -### Arc Clone Cost - -```rust -// Memory allocations: -let arc1 = Arc::new(large_data); // 1 heap allocation -let arc2 = arc1.clone(); // 0 heap allocations ← IMPORTANT - -// Memory usage: -// - large_data: allocated once, shared -// - Arc overhead: 16 bytes per Arc (pointer + refcount) -// - Total data copies: ZERO -``` - -## Proof of Shallow Cloning - -### Test Case: Track Allocations - -```rust -use std::sync::atomic::{AtomicUsize, Ordering}; - -static CREATED: AtomicUsize = AtomicUsize::new(0); -static CLONED: AtomicUsize = AtomicUsize::new(0); - -#[derive(Clone)] -struct TrackedData { - value: String, -} - -impl TrackedData { - fn new(s: String) -> Self { - CREATED.fetch_add(1, Ordering::SeqCst); - Self { value: s } - } -} - -impl Clone for TrackedData { - fn clone(&self) -> Self { - CLONED.fetch_add(1, Ordering::SeqCst); - Self { value: self.value.clone() } - } -} - -// Test: -let data = Arc::new(Mutex::new(TrackedData::new("test".into()))); -let lock_kp = LockKp::new(/* paths to Arc> */); - -// Compose multiple times: -let composed = lock_kp1.compose(lock_kp2).compose(lock_kp3); - -// Result: -assert_eq!(CREATED.load(), 1); // Only 1 creation -assert_eq!(CLONED.load(), 0); // ZERO clones of TrackedData! -``` - -**The Arc is cloned multiple times, but TrackedData is NEVER cloned.** - -## Best Practices - -### ✅ DO: Use Arc> for shared state - -```rust -struct Config { - data: Arc>, // Good: shallow clones -} - -let lock_kp = LockKp::new(/* ... */); -let composed = lock_kp1.compose(lock_kp2); // Cheap! -``` - -### ❌ DON'T: Clone the inner data manually - -```rust -// Bad: Deep clones the data -let cloned_data = expensive_data.clone(); - -// Good: Arc clone is shallow -let cloned_arc = arc_data.clone(); // Just increments refcount -``` - -### ✅ DO: Compose multiple lock levels freely - -```rust -// This is cheap - only atomic increments -let deep_path = lock1 - .compose(lock2) - .compose(lock3) - .compose(lock4); -``` - -## Guarantees - -1. **No Deep Cloning**: The inner data `T` in `Arc>` is NEVER cloned -2. **Constant Time**: All clone operations are O(1) -3. **No Heap Allocations**: Cloning Arc doesn't allocate memory -4. **Thread Safe**: Arc refcounting is atomic and thread-safe -5. **Memory Efficient**: Only one copy of data exists in memory - -## Conclusion - -**Every single clone operation in the lock module is a shallow clone:** - -- `LockKp::clone()` → Copies function pointers only -- `Arc::clone()` → Increments refcount only -- `ArcMutexAccess::clone()` → Zero-cost (PhantomData) - -**No deep data cloning ever occurs.** The module is designed specifically to avoid expensive data copies while maintaining type safety and composability. diff --git a/SMART_POINTER_DEREF.md b/SMART_POINTER_DEREF.md deleted file mode 100644 index dc82afe..0000000 --- a/SMART_POINTER_DEREF.md +++ /dev/null @@ -1,131 +0,0 @@ -# Smart Pointer Dereferencing in Key-Paths-Derive - -## Summary - -The `#[derive(Kp)]` macro correctly handles smart pointer types (`Box`, `Rc`, `Arc`) by **automatically dereferencing** them to return references to the inner value. - -## Behavior - -### Box -- **Generated Keypath Type**: `KpType<'static, StructName, T>` (not `Box`) -- **Get Returns**: `Option<&T>` (not `Option<&Box>`) -- **Get Mut Returns**: `Option<&mut T>` (not `Option<&mut Box>`) -- **Implementation**: Uses `&*field` and `&mut *field` to dereference - -### Rc -- **Generated Keypath Type**: `KpType<'static, StructName, T>` (not `Rc`) -- **Get Returns**: `Option<&T>` (not `Option<&Rc>`) -- **Get Mut Returns**: `Option<&mut T>` when only one reference exists, `None` otherwise -- **Implementation**: Uses `&*field` and `Rc::get_mut(&mut field)` - -### Arc -- **Generated Keypath Type**: `KpType<'static, StructName, T>` (not `Arc`) -- **Get Returns**: `Option<&T>` (not `Option<&Arc>`) -- **Get Mut Returns**: `Option<&mut T>` when only one reference exists, `None` otherwise -- **Implementation**: Uses `&*field` and `Arc::get_mut(&mut field)` - -## Example - -```rust -use key_paths_derive::Kp; -use rust_key_paths::KpType; - -#[derive(Kp)] -struct MyData { - boxed_value: Box, - rc_value: std::rc::Rc, - arc_value: std::sync::Arc, -} - -let data = MyData { - boxed_value: Box::new(42), - rc_value: std::rc::Rc::new("hello".to_string()), - arc_value: std::sync::Arc::new(3.14), -}; - -// Box field returns &i32 -let box_kp: KpType<'static, MyData, i32> = MyData::boxed_value(); -let value: Option<&i32> = box_kp.get(&data); -assert_eq!(value, Some(&42)); - -// Rc field returns &String -let rc_kp: KpType<'static, MyData, String> = MyData::rc_value(); -let value: Option<&String> = rc_kp.get(&data); -assert_eq!(value.map(|s| s.as_str()), Some("hello")); - -// Arc field returns &f64 -let arc_kp: KpType<'static, MyData, f64> = MyData::arc_value(); -let value: Option<&f64> = arc_kp.get(&data); -assert_eq!(value, Some(&3.14)); - -// Mutable access through Box -let mut data = MyData { - boxed_value: Box::new(10), - rc_value: std::rc::Rc::new("test".to_string()), - arc_value: std::sync::Arc::new(1.0), -}; - -let box_kp = MyData::boxed_value(); -let value: Option<&mut i32> = box_kp.get_mut(&mut data); -*value.unwrap() = 100; -assert_eq!(*data.boxed_value, 100); -``` - -## Benefits - -1. **Ergonomic API**: Users work with the inner type directly, not the wrapper -2. **Type Safety**: The type system enforces correct usage -3. **Consistent with Rust idioms**: Follows the same pattern as Deref coercion -4. **Mutable Access**: Smart pointer mutable access works when semantically valid (single reference for Rc/Arc) - -## Implementation Details - -### Code Generation - -For `Box`: -```rust -pub fn field_name() -> KpType<'static, StructName, T> { - Kp::new( - |root: &StructName| Some(&*root.field_name), - |root: &mut StructName| Some(&mut *root.field_name), - ) -} -``` - -For `Rc`: -```rust -pub fn field_name() -> KpType<'static, StructName, T> { - Kp::new( - |root: &StructName| Some(&*root.field_name), - |root: &mut StructName| { - std::rc::Rc::get_mut(&mut root.field_name) - }, - ) -} -``` - -For `Arc`: -```rust -pub fn field_name() -> KpType<'static, StructName, T> { - Kp::new( - |root: &StructName| Some(&*root.field_name), - |root: &mut StructName| { - std::sync::Arc::get_mut(&mut root.field_name) - }, - ) -} -``` - -## Test Coverage - -All smart pointer dereferencing behavior is verified with comprehensive tests: -- `test_box_returns_inner_type` - Verifies Box returns &T -- `test_rc_returns_inner_type` - Verifies Rc returns &T -- `test_arc_returns_inner_type` - Verifies Arc returns &T -- `test_box_mutable_returns_inner_type` - Verifies Box mutable access -- `test_rc_access` - Verifies Rc mutable access with single reference -- `test_arc_access` - Verifies Arc mutable access with single reference -- `test_rc_no_mut_with_multiple_refs` - Verifies Rc returns None with multiple refs -- `test_arc_no_mut_with_multiple_refs` - Verifies Arc returns None with multiple refs - -Total: **40 tests passing** (18 wrapper types + 5 derive + 13 comprehensive + 4 smart pointer deref) diff --git a/TRAIT_BOUNDS_ANALYSIS.md b/TRAIT_BOUNDS_ANALYSIS.md deleted file mode 100644 index 075f123..0000000 --- a/TRAIT_BOUNDS_ANALYSIS.md +++ /dev/null @@ -1,163 +0,0 @@ -# Trait Bounds Analysis: Copy and 'static - -## Executive Summary - -**Do `Copy` and `'static` trait bounds cause cloning or memory leaks?** - -**NO** - These bounds do NOT cause cloning or memory leaks: - -1. **`Copy` bound applies to closures, NOT data**: The `F: Copy` bound means the function/closure must be `Copy`, not the `Root` or `Value` types being processed. - -2. **`'static` does NOT mean data lives forever**: It means the TYPE doesn't contain non-`'static` references. Data can still be dropped normally. - -3. **Operations work through references**: Keypaths operate on `&Root` and `&Value`, so no cloning of the actual data structures occurs. - -4. **No memory leaks**: All 12 comprehensive tests confirm proper drop behavior and reference counting. - ---- - -## Test Coverage - -### 1. Non-Cloneable Root Types (`test_no_clone_required_for_root`) -- **Verified**: Root types that are NOT `Clone` and NOT `Copy` work perfectly -- **Proof**: `NonCloneableRoot` with `Arc` compiles and runs -- **Conclusion**: Keypaths don't require `Clone` on `Root` - -### 2. Non-Cloneable Value Types (`test_no_clone_required_for_value`) -- **Verified**: Value types that are NOT `Clone` and NOT `Copy` work perfectly -- **Proof**: `NonCloneableValue` with `Arc` works in keypaths -- **Conclusion**: Keypaths don't require `Clone` on `Value` - -### 3. Memory Leak Detection (`test_static_does_not_leak_memory`) -- **Verified**: Objects are created and dropped exactly once -- **Tracking**: `CREATED` counter = `DROPPED` counter = 1 -- **Proof**: Multiple derived keypaths don't cause extra allocations -- **Conclusion**: No memory leaks from `'static` bound - -### 4. Large Data Structures (`test_references_not_cloned`) -- **Verified**: 1MB data structures processed without cloning -- **Test**: `ExpensiveData` with `Vec` (1 million bytes) -- **Operations**: `map` and `filter` work through references only -- **Conclusion**: No expensive cloning occurs - -### 5. Arc Reference Counting (`test_hof_with_arc_no_extra_clones`) -- **Verified**: `Arc::strong_count` remains constant during operations -- **Initial**: 1 reference -- **During use**: 2 references (original + root) -- **After map/filter**: Still 2 references (no extra clones) -- **After drop**: Back to 1 reference -- **Conclusion**: No hidden Arc clones from HOFs - -### 6. Closure Captures (`test_closure_captures_not_root_values`) -- **Verified**: Closures capture external state, not root/value data -- **Test**: `Arc` tracks call count -- **Behavior**: Only the closure state is moved, not the data -- **Conclusion**: Closures capture minimal state - -### 7. Temporary Data (`test_static_with_borrowed_data`) -- **Verified**: `'static` doesn't prevent normal dropping -- **Test**: Non-static `Root` with `String` data -- **Behavior**: Data is dropped when it goes out of scope -- **Conclusion**: `'static` ≠ "lives forever" - -### 8. Accumulation Test (`test_multiple_hof_operations_no_accumulation`) -- **Verified**: Multiple HOF operations don't accumulate data -- **Test**: Vec of `Tracked` objects with drop counter -- **Operations**: `count_items`, `sum_value`, `any`, `all` -- **Drop count**: Exactly 3 (one per object) -- **Conclusion**: No hidden accumulation - -### 9. Function vs Data Copying (`test_copy_bound_only_for_function_not_data`) -- **Verified**: `F: Copy` applies to function, not data -- **Test**: `NonCopyData` with `String` (not Copy) -- **Operations**: `map` and `filter` work perfectly -- **Conclusion**: `Copy` bound is for closure, not data - -### 10. Cyclic References (`test_no_memory_leak_with_cyclic_references`) -- **Verified**: Weak pointers and Arc cycles don't leak -- **Test**: Node structure with `Weak` parent -- **Drop tracking**: Exactly 1 drop after scope exit -- **Conclusion**: No circular reference leaks - -### 11. Zero-Cost Abstraction (`test_hof_operations_are_zero_cost_abstractions`) -- **Verified**: HOFs produce identical results to direct access -- **Comparison**: Direct `get().map()` vs `map().get()` -- **Result**: Identical outputs -- **Conclusion**: No overhead from HOF layer - -### 12. Complex Closure Captures (`test_complex_closure_captures_allowed`) -- **Verified**: Removing `Copy` allows richer closures -- **Test**: Capture `threshold` and `Arc` -- **Benefit**: More flexible closure patterns -- **Conclusion**: Optimized HOFs (without `Copy`) enable better ergonomics - ---- - -## Why These Bounds Exist - -### `Copy` Bound (for `map`, `filter`, `filter_map`, `inspect`) -- **Reason**: These closures are used in BOTH getter and setter of the returned `Kp` -- **Mechanism**: Closure is copied into both the new getter closure and new setter closure -- **Alternative**: Would require `Arc` or `Rc` to share the closure -- **Performance**: Copying small function pointers is cheaper than reference counting - -### `'static` Bound (for all HOF closures) -- **Reason**: The returned `Kp` struct owns its closures completely -- **Mechanism**: Closures become part of the returned type signature -- **Constraint**: Rust requires owned closures to be `'static` (no dangling references) -- **Not a limitation**: Data passed to closures is always via references (`&Root`, `&Value`) - ---- - -## Optimizations Made - -We removed `Copy` from 10 out of 16 HOF methods: -- ✅ `flat_map` - captures closure once -- ✅ `fold_value` - captures closure once -- ✅ `any` - captures closure once -- ✅ `all` - captures closure once -- ✅ `count_items` - captures closure once -- ✅ `find_in` - captures closure once -- ✅ `take` - captures closure once -- ✅ `skip` - captures closure once -- ✅ `partition_value` - captures closure once -- ✅ `min_value` - captures closure once -- ✅ `max_value` - captures closure once -- ✅ `sum_value` - captures closure once - -Kept `Copy` for 4 methods: -- ❌ `map` - used in both getter and setter -- ❌ `filter` - used in both getter and setter -- ❌ `filter_map` - used in both getter and setter -- ❌ `inspect` - used in both getter and setter - ---- - -## Key Insights - -1. **References all the way down**: Keypaths operate exclusively on references (`&Root` → `&Value`), so the actual data structures are never cloned during operations. - -2. **Function pointers are cheap**: Copying a function pointer (which is what `Copy` does for closures without captures) is typically just copying a pointer (8 bytes on 64-bit systems). - -3. **Captured state != processed data**: When closures capture state (via `move`), they're capturing the closure's environment, not the `Root` or `Value` being processed. - -4. **'static is about types, not lifetimes**: The `'static` bound means "this type doesn't contain borrowed references", not "this data lives for the entire program". - -5. **Zero-cost abstractions work**: The Rust compiler can optimize away the HOF layers, producing machine code equivalent to hand-written versions. - ---- - -## Conclusion - -The `Copy` and `'static` trait bounds on HOF closures are: -- ✅ **Safe**: No memory leaks -- ✅ **Efficient**: No unnecessary cloning -- ✅ **Necessary**: Required by Rust's type system for owned closures -- ✅ **Minimal**: Apply only to closures, not to data being processed - -The comprehensive test suite (12 tests, 48 total passing) verifies that: -- Non-cloneable types work perfectly -- Reference counts remain stable -- Memory is properly freed -- Large data structures aren't cloned -- Temporary data is dropped correctly diff --git a/benches/akp_cpu_bench.rs b/benches/akp_cpu_bench.rs index 4a24131..5b07bd6 100644 --- a/benches/akp_cpu_bench.rs +++ b/benches/akp_cpu_bench.rs @@ -6,10 +6,10 @@ //! Run: `cargo bench --bench akp_cpu_bench` //! Requires key-paths-iter with features = ["rayon", "gpu"] in dev-dependencies. -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; use key_paths_derive::Kp; use key_paths_iter::wgpu::{ - cpu_transform_f32, numeric_akp_f32, GpuValue, IntoNumericAKp, NumericAKp, WgpuContext, + GpuValue, IntoNumericAKp, NumericAKp, WgpuContext, cpu_transform_f32, numeric_akp_f32, }; use rayon::prelude::*; use std::sync::Arc; @@ -65,11 +65,9 @@ fn run_numeric_sequential(roots: &[impl AsAny], kp: &NumericAKp) -> Vec { fn run_numeric_rayon(roots: &[impl AsAny + Sync], kp: &NumericAKp) -> Vec { roots .par_iter() - .map(|root| { - match (kp.extractor)(root.as_any()) { - Some(GpuValue::F32(f)) => cpu_transform_f32(f), - _ => 0.0, - } + .map(|root| match (kp.extractor)(root.as_any()) { + Some(GpuValue::F32(f)) => cpu_transform_f32(f), + _ => 0.0, }) .collect() } @@ -147,7 +145,9 @@ fn bench_akp_numeric(c: &mut Criterion) { group_kp.bench_function(format!("sequential_{}", n_roots), |b| { b.iter_batched( || make_users_kp(n_roots), - |roots| run_numeric_sequential(black_box(&roots), black_box(score_numeric_kp.as_ref())), + |roots| { + run_numeric_sequential(black_box(&roots), black_box(score_numeric_kp.as_ref())) + }, BatchSize::SmallInput, ); }); @@ -164,7 +164,13 @@ fn bench_akp_numeric(c: &mut Criterion) { group_kp.bench_function(format!("gpu_{}", n_roots), |b| { b.iter_batched( || make_users_kp(n_roots), - |roots| run_numeric_gpu(black_box(&roots), black_box(score_numeric_kp.as_ref()), ctx), + |roots| { + run_numeric_gpu( + black_box(&roots), + black_box(score_numeric_kp.as_ref()), + ctx, + ) + }, BatchSize::SmallInput, ); }); diff --git a/benches/deep_chain_async_only.rs b/benches/deep_chain_async_only.rs index be1ec80..ca52ed9 100644 --- a/benches/deep_chain_async_only.rs +++ b/benches/deep_chain_async_only.rs @@ -7,9 +7,9 @@ //! - Keypath approach: Kp.then_async(AsyncLockKp).then().then() chain //! - Direct lock approach: tokio_mutex.lock().await, then access leaf -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rust_key_paths::async_lock::{AsyncLockKp, TokioMutexAccess}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use rust_key_paths::Kp; +use rust_key_paths::async_lock::{AsyncLockKp, TokioMutexAccess}; use std::sync::Arc; use tokio::runtime::Runtime; @@ -59,11 +59,10 @@ fn build_and_get<'a>(root: &'a Root, rt: &Runtime) -> Option<&'a i32> { ); let async_l1 = AsyncLockKp::new(prev, TokioMutexAccess::new(), next); - let kp_root_to_lock: rust_key_paths::KpType>> = - Kp::new( - |r: &Root| Some(&r.tokio_mutex), - |r: &mut Root| Some(&mut r.tokio_mutex), - ); + let kp_root_to_lock: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.tokio_mutex), + |r: &mut Root| Some(&mut r.tokio_mutex), + ); let step1 = kp_root_to_lock.then_async(async_l1); rt.block_on(step1.get(root)) @@ -81,11 +80,10 @@ fn build_and_get_mut<'a>(root: &'a mut Root, rt: &Runtime) -> Option<&'a mut i32 ); let async_l1 = AsyncLockKp::new(prev, TokioMutexAccess::new(), next); - let kp_root_to_lock: rust_key_paths::KpType>> = - Kp::new( - |r: &Root| Some(&r.tokio_mutex), - |r: &mut Root| Some(&mut r.tokio_mutex), - ); + let kp_root_to_lock: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.tokio_mutex), + |r: &mut Root| Some(&mut r.tokio_mutex), + ); let step1 = kp_root_to_lock.then_async(async_l1); rt.block_on(step1.get_mut(root)) diff --git a/benches/deep_chain_leaf.rs b/benches/deep_chain_leaf.rs index 2c5e7f4..2c184b9 100644 --- a/benches/deep_chain_leaf.rs +++ b/benches/deep_chain_leaf.rs @@ -6,16 +6,16 @@ //! - Direct lock approach: sync_mutex.lock(), tokio_mutex.lock().await, then access leaf //! - Keypath approach: LockKp.then().then().then_async().then() chain -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use std::sync::{Arc, Mutex}; +#[cfg(all(feature = "tokio", feature = "parking_lot"))] +use rust_key_paths::Kp; #[cfg(all(feature = "tokio", feature = "parking_lot"))] use rust_key_paths::async_lock::{AsyncLockKp, TokioMutexAccess}; #[cfg(all(feature = "tokio", feature = "parking_lot"))] use rust_key_paths::lock::{ArcMutexAccess, LockKp}; #[cfg(all(feature = "tokio", feature = "parking_lot"))] -use rust_key_paths::Kp; -#[cfg(all(feature = "tokio", feature = "parking_lot"))] use tokio::runtime::Runtime; #[cfg(all(feature = "tokio", feature = "parking_lot"))] @@ -61,18 +61,21 @@ mod benches { pub fn build_and_get<'a>(root: &'a Root, rt: &Runtime) -> Option<&'a i32> { let identity_l1: rust_key_paths::KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); - let kp_sync: rust_key_paths::KpType>> = - Kp::new(|r: &Root| Some(&r.sync_mutex), |r: &mut Root| Some(&mut r.sync_mutex)); + let kp_sync: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.sync_mutex), + |r: &mut Root| Some(&mut r.sync_mutex), + ); let lock_root_to_l1 = LockKp::new(kp_sync, ArcMutexAccess::new(), identity_l1); - let kp_l1_inner: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); + let kp_l1_inner: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); - let kp_l2_tokio: rust_key_paths::KpType>> = - Kp::new( - |l: &Level2| Some(&l.tokio_mutex), - |l: &mut Level2| Some(&mut l.tokio_mutex), - ); + let kp_l2_tokio: rust_key_paths::KpType>> = Kp::new( + |l: &Level2| Some(&l.tokio_mutex), + |l: &mut Level2| Some(&mut l.tokio_mutex), + ); let async_l3 = { let prev: rust_key_paths::KpType< @@ -84,8 +87,10 @@ mod benches { AsyncLockKp::new(prev, TokioMutexAccess::new(), next) }; - let kp_l3_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l3_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = lock_root_to_l1.then(kp_l1_inner); let step2 = step1.then(kp_l2_tokio); @@ -99,18 +104,21 @@ mod benches { pub fn build_and_get_mut<'a>(root: &'a mut Root, rt: &Runtime) -> Option<&'a mut i32> { let identity_l1: rust_key_paths::KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); - let kp_sync: rust_key_paths::KpType>> = - Kp::new(|r: &Root| Some(&r.sync_mutex), |r: &mut Root| Some(&mut r.sync_mutex)); + let kp_sync: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.sync_mutex), + |r: &mut Root| Some(&mut r.sync_mutex), + ); let lock_root_to_l1 = LockKp::new(kp_sync, ArcMutexAccess::new(), identity_l1); - let kp_l1_inner: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); + let kp_l1_inner: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); - let kp_l2_tokio: rust_key_paths::KpType>> = - Kp::new( - |l: &Level2| Some(&l.tokio_mutex), - |l: &mut Level2| Some(&mut l.tokio_mutex), - ); + let kp_l2_tokio: rust_key_paths::KpType>> = Kp::new( + |l: &Level2| Some(&l.tokio_mutex), + |l: &mut Level2| Some(&mut l.tokio_mutex), + ); let async_l3 = { let prev: rust_key_paths::KpType< @@ -122,8 +130,10 @@ mod benches { AsyncLockKp::new(prev, TokioMutexAccess::new(), next) }; - let kp_l3_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l3_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = lock_root_to_l1.then(kp_l1_inner); let step2 = step1.then(kp_l2_tokio); @@ -135,7 +145,7 @@ mod benches { #[cfg(all(feature = "tokio", feature = "parking_lot"))] fn bench_deep_chain_leaf_read(c: &mut Criterion) { - use crate::benches::{build_and_get, make_root, Level3}; + use crate::benches::{Level3, build_and_get, make_root}; let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("deep_chain_leaf_read"); @@ -171,7 +181,7 @@ fn bench_deep_chain_leaf_read(c: &mut Criterion) { #[cfg(all(feature = "tokio", feature = "parking_lot"))] fn bench_deep_chain_leaf_write(c: &mut Criterion) { - use crate::benches::{build_and_get_mut, make_root, Level3}; + use crate::benches::{Level3, build_and_get_mut, make_root}; let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("deep_chain_leaf_write"); diff --git a/benches/deep_chain_sync_only.rs b/benches/deep_chain_sync_only.rs index 30c7687..8d28f99 100644 --- a/benches/deep_chain_sync_only.rs +++ b/benches/deep_chain_sync_only.rs @@ -6,9 +6,9 @@ //! - Keypath approach: LockKp.then().then_lock().then() chain (two sync Mutex levels) //! - Direct lock approach: sync_mutex1.lock(), sync_mutex2.lock(), then access leaf -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rust_key_paths::lock::{ArcMutexAccess, LockKp}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use rust_key_paths::Kp; +use rust_key_paths::lock::{ArcMutexAccess, LockKp}; use std::sync::{Arc, Mutex}; // Root -> Arc> @@ -49,24 +49,29 @@ fn make_root() -> Root { fn build_and_get(root: &Root) -> Option<&i32> { let identity_l1: rust_key_paths::KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); - let kp_sync1: rust_key_paths::KpType>> = - Kp::new(|r: &Root| Some(&r.sync_mutex_1), |r: &mut Root| Some(&mut r.sync_mutex_1)); + let kp_sync1: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.sync_mutex_1), + |r: &mut Root| Some(&mut r.sync_mutex_1), + ); let lock_root_to_l1 = LockKp::new(kp_sync1, ArcMutexAccess::new(), identity_l1); - let kp_l1_inner: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); + let kp_l1_inner: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); let identity_l3: rust_key_paths::KpType = Kp::new(|l: &Level3| Some(l), |l: &mut Level3| Some(l)); - let kp_sync2: rust_key_paths::KpType>> = - Kp::new( - |l: &Level2| Some(&l.sync_mutex_2), - |l: &mut Level2| Some(&mut l.sync_mutex_2), - ); + let kp_sync2: rust_key_paths::KpType>> = Kp::new( + |l: &Level2| Some(&l.sync_mutex_2), + |l: &mut Level2| Some(&mut l.sync_mutex_2), + ); let lock_l2_to_l3 = LockKp::new(kp_sync2, ArcMutexAccess::new(), identity_l3); - let kp_l3_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l3_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = lock_root_to_l1.then(kp_l1_inner); let step2 = step1.then_lock(lock_l2_to_l3); @@ -78,24 +83,29 @@ fn build_and_get(root: &Root) -> Option<&i32> { fn build_and_get_mut(root: &mut Root) -> Option<&mut i32> { let identity_l1: rust_key_paths::KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); - let kp_sync1: rust_key_paths::KpType>> = - Kp::new(|r: &Root| Some(&r.sync_mutex_1), |r: &mut Root| Some(&mut r.sync_mutex_1)); + let kp_sync1: rust_key_paths::KpType>> = Kp::new( + |r: &Root| Some(&r.sync_mutex_1), + |r: &mut Root| Some(&mut r.sync_mutex_1), + ); let lock_root_to_l1 = LockKp::new(kp_sync1, ArcMutexAccess::new(), identity_l1); - let kp_l1_inner: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); + let kp_l1_inner: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); let identity_l3: rust_key_paths::KpType = Kp::new(|l: &Level3| Some(l), |l: &mut Level3| Some(l)); - let kp_sync2: rust_key_paths::KpType>> = - Kp::new( - |l: &Level2| Some(&l.sync_mutex_2), - |l: &mut Level2| Some(&mut l.sync_mutex_2), - ); + let kp_sync2: rust_key_paths::KpType>> = Kp::new( + |l: &Level2| Some(&l.sync_mutex_2), + |l: &mut Level2| Some(&mut l.sync_mutex_2), + ); let lock_l2_to_l3 = LockKp::new(kp_sync2, ArcMutexAccess::new(), identity_l3); - let kp_l3_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l3_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = lock_root_to_l1.then(kp_l1_inner); let step2 = step1.then_lock(lock_l2_to_l3); diff --git a/benches/keypath_vs_unwrap.rs b/benches/keypath_vs_unwrap.rs index f089bd1..25f9fbd 100644 --- a/benches/keypath_vs_unwrap.rs +++ b/benches/keypath_vs_unwrap.rs @@ -1,4 +1,4 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use key_paths_derive::Kp; use parking_lot::RwLock; use std::sync::Arc; diff --git a/benches/kp_plain_only.rs b/benches/kp_plain_only.rs index a475156..f4f7e7e 100644 --- a/benches/kp_plain_only.rs +++ b/benches/kp_plain_only.rs @@ -6,7 +6,7 @@ //! - Keypath approach: Kp.then().then().then() chain //! - Direct field access: root.l1.inner.leaf -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use rust_key_paths::Kp; // Root -> Level1 @@ -47,12 +47,18 @@ fn make_root() -> Root { fn build_and_get(root: &Root) -> Option<&i32> { let kp_l1: rust_key_paths::KpType = Kp::new(|r: &Root| Some(&r.l1), |r: &mut Root| Some(&mut r.l1)); - let kp_l2: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); - let kp_l3: rust_key_paths::KpType = - Kp::new(|l: &Level2| Some(&l.inner), |l: &mut Level2| Some(&mut l.inner)); - let kp_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l2: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); + let kp_l3: rust_key_paths::KpType = Kp::new( + |l: &Level2| Some(&l.inner), + |l: &mut Level2| Some(&mut l.inner), + ); + let kp_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = kp_l1.then(kp_l2); let step2 = step1.then(kp_l3); let chain = step2.then(kp_leaf); @@ -63,12 +69,18 @@ fn build_and_get(root: &Root) -> Option<&i32> { fn build_and_get_mut(root: &mut Root) -> Option<&mut i32> { let kp_l1: rust_key_paths::KpType = Kp::new(|r: &Root| Some(&r.l1), |r: &mut Root| Some(&mut r.l1)); - let kp_l2: rust_key_paths::KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); - let kp_l3: rust_key_paths::KpType = - Kp::new(|l: &Level2| Some(&l.inner), |l: &mut Level2| Some(&mut l.inner)); - let kp_leaf: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l2: rust_key_paths::KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); + let kp_l3: rust_key_paths::KpType = Kp::new( + |l: &Level2| Some(&l.inner), + |l: &mut Level2| Some(&mut l.inner), + ); + let kp_leaf: rust_key_paths::KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); let step1 = kp_l1.then(kp_l2); let step2 = step1.then(kp_l3); let chain = step2.then(kp_leaf); diff --git a/benches/scale_par_bench.rs b/benches/scale_par_bench.rs index 784fa6a..77ef842 100644 --- a/benches/scale_par_bench.rs +++ b/benches/scale_par_bench.rs @@ -5,16 +5,21 @@ //! - Validation (all non-empty): sequential iter().all vs par_validate_buffers_non_empty //! - Count by predicate: sequential filter().count vs par_count_by (nodes by kind) -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use criterion::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; use key_paths_iter::query_par::ParallelCollectionKeyPath; use key_paths_iter::scale_par::{ - par_scale_buffers, par_validate_buffers_non_empty, ComputeState, GpuComputePipeline, - GpuBuffer, InteractionNet, NetNode, NodeKind, + ComputeState, GpuBuffer, GpuComputePipeline, InteractionNet, NetNode, NodeKind, + par_scale_buffers, par_validate_buffers_non_empty, }; use rust_key_paths::Kp; use rust_key_paths::KpType; -fn make_pipeline(num_buffers: usize, buffer_len: usize, num_nodes: usize, num_pairs: usize) -> GpuComputePipeline { +fn make_pipeline( + num_buffers: usize, + buffer_len: usize, + num_nodes: usize, + num_pairs: usize, +) -> GpuComputePipeline { GpuComputePipeline { cpu_state: ComputeState { buffers: (0..num_buffers) @@ -37,10 +42,24 @@ fn make_pipeline(num_buffers: usize, buffer_len: usize, num_nodes: usize, num_pa 2 => NodeKind::Dup, _ => NodeKind::Ref, }; - NetNode::new(kind, [i as u32 % 1000, (i + 1) as u32 % 1000, (i + 2) as u32 % 1000]) + NetNode::new( + kind, + [ + i as u32 % 1000, + (i + 1) as u32 % 1000, + (i + 2) as u32 % 1000, + ], + ) + }) + .collect(), + active_pairs: (0..num_pairs) + .map(|i| { + ( + i as u32 % num_nodes as u32, + (i + 1) as u32 % num_nodes as u32, + ) }) .collect(), - active_pairs: (0..num_pairs).map(|i| (i as u32 % num_nodes as u32, (i + 1) as u32 % num_nodes as u32)).collect(), }, } } @@ -54,11 +73,20 @@ fn sequential_scale_buffers(pipeline: &mut GpuComputePipeline, scale: f32) { } fn sequential_validate_buffers_non_empty(pipeline: &GpuComputePipeline) -> bool { - pipeline.cpu_state.buffers.iter().all(|b| !b.data.is_empty()) + pipeline + .cpu_state + .buffers + .iter() + .all(|b| !b.data.is_empty()) } fn sequential_count_nodes_era(pipeline: &GpuComputePipeline) -> usize { - pipeline.reduction_net.nodes.iter().filter(|n| n.kind() == NodeKind::Era).count() + pipeline + .reduction_net + .nodes + .iter() + .filter(|n| n.kind() == NodeKind::Era) + .count() } fn bench_buffer_scale(c: &mut Criterion) { @@ -66,29 +94,35 @@ fn bench_buffer_scale(c: &mut Criterion) { group.sample_size(50); for (num_buffers, buffer_len) in [(100, 1000), (500, 2000), (1000, 1000)] { - group.bench_function(format!("sequential_{}buf_x{}", num_buffers, buffer_len), |b| { - b.iter_batched( - || make_pipeline(num_buffers, buffer_len, 1000, 2000), - |mut pipeline| { - sequential_scale_buffers(black_box(&mut pipeline), 2.0); - }, - BatchSize::SmallInput, - ); - }); + group.bench_function( + format!("sequential_{}buf_x{}", num_buffers, buffer_len), + |b| { + b.iter_batched( + || make_pipeline(num_buffers, buffer_len, 1000, 2000), + |mut pipeline| { + sequential_scale_buffers(black_box(&mut pipeline), 2.0); + }, + BatchSize::SmallInput, + ); + }, + ); let buffers_kp: KpType<'static, GpuComputePipeline, Vec> = Kp::new( |p: &GpuComputePipeline| Some(&p.cpu_state.buffers), |p: &mut GpuComputePipeline| Some(&mut p.cpu_state.buffers), ); - group.bench_function(format!("keypath_par_{}buf_x{}", num_buffers, buffer_len), |b| { - b.iter_batched( - || make_pipeline(num_buffers, buffer_len, 1000, 2000), - |mut pipeline| { - par_scale_buffers(black_box(&buffers_kp), black_box(&mut pipeline), 2.0); - }, - BatchSize::SmallInput, - ); - }); + group.bench_function( + format!("keypath_par_{}buf_x{}", num_buffers, buffer_len), + |b| { + b.iter_batched( + || make_pipeline(num_buffers, buffer_len, 1000, 2000), + |mut pipeline| { + par_scale_buffers(black_box(&buffers_kp), black_box(&mut pipeline), 2.0); + }, + BatchSize::SmallInput, + ); + }, + ); } group.finish(); @@ -112,7 +146,11 @@ fn bench_validation(c: &mut Criterion) { ); group.bench_function( format!("keypath_par_all_non_empty_{}buf", num_buffers), - |b| b.iter(|| par_validate_buffers_non_empty(black_box(&buffers_kp), black_box(&pipeline))), + |b| { + b.iter(|| { + par_validate_buffers_non_empty(black_box(&buffers_kp), black_box(&pipeline)) + }) + }, ); } @@ -134,9 +172,14 @@ fn bench_count_by(c: &mut Criterion) { |p: &GpuComputePipeline| Some(&p.reduction_net.nodes), |p: &mut GpuComputePipeline| Some(&mut p.reduction_net.nodes), ); - group.bench_function(format!("keypath_par_count_by_era_{}nodes", num_nodes), |b| { - b.iter(|| nodes_kp.par_count_by(black_box(&pipeline), |n| n.kind() == NodeKind::Era)); - }); + group.bench_function( + format!("keypath_par_count_by_era_{}nodes", num_nodes), + |b| { + b.iter(|| { + nodes_kp.par_count_by(black_box(&pipeline), |n| n.kind() == NodeKind::Era) + }); + }, + ); } group.finish(); diff --git a/benches/ten_level_arc_rwlock.rs b/benches/ten_level_arc_rwlock.rs index 81ac30f..e056089 100644 --- a/benches/ten_level_arc_rwlock.rs +++ b/benches/ten_level_arc_rwlock.rs @@ -7,7 +7,7 @@ #![cfg(feature = "parking_lot")] -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use key_paths_derive::Kp; use std::sync::Arc; diff --git a/benches/ten_level_std_rwlock.rs b/benches/ten_level_std_rwlock.rs index 21a3074..a52ad45 100644 --- a/benches/ten_level_std_rwlock.rs +++ b/benches/ten_level_std_rwlock.rs @@ -5,7 +5,7 @@ //! - **Dynamic keypath**: LockKp chain built each iteration //! - **Direct lock acquire**: Manual .read() through 10 levels -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use key_paths_derive::Kp; use std::sync::{Arc, RwLock}; diff --git a/benches/ten_level_tokio_rwlock.rs b/benches/ten_level_tokio_rwlock.rs index fa3b842..88651a1 100644 --- a/benches/ten_level_tokio_rwlock.rs +++ b/benches/ten_level_tokio_rwlock.rs @@ -7,7 +7,7 @@ #![cfg(feature = "tokio")] -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use key_paths_derive::Kp; use std::sync::Arc; use tokio::runtime::Runtime; diff --git a/examples/adaptive_pool_example.rs b/examples/adaptive_pool_example.rs index 92b7d6a..95299e9 100644 --- a/examples/adaptive_pool_example.rs +++ b/examples/adaptive_pool_example.rs @@ -17,14 +17,21 @@ fn main() { let p = pool.get_pool(); let start = Instant::now(); let _sum: u32 = p.install(|| data.par_iter().copied().sum()); - println!("After adjust_for_load(20%%): pool installed sum in {:?}", start.elapsed()); + println!( + "After adjust_for_load(20%%): pool installed sum in {:?}", + start.elapsed() + ); // Simulate "high load" -> use full pool (need to wait 5s for adjustment, so we just get_pool again) pool.adjust_for_load(90.0); let p = pool.get_pool(); let start = Instant::now(); let sum: u32 = p.install(|| data.par_iter().copied().sum()); - println!("After adjust_for_load(90%%): pool installed sum = {} in {:?}", sum, start.elapsed()); + println!( + "After adjust_for_load(90%%): pool installed sum = {} in {:?}", + sum, + start.elapsed() + ); println!("\nNote: adjust_for_load only changes pool every 5 seconds."); println!("Done."); diff --git a/examples/advanced_query_builder.rs b/examples/advanced_query_builder.rs index 170d45f..aed7e95 100644 --- a/examples/advanced_query_builder.rs +++ b/examples/advanced_query_builder.rs @@ -15,9 +15,9 @@ // use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; // use keypaths_proc::Kp; -use std::collections::HashMap; use key_paths_derive::Kp; use rust_key_paths::KpType; +use std::collections::HashMap; #[derive(Debug, Clone, Kp)] struct Product { @@ -52,7 +52,11 @@ where } // Add a filter predicate (KpType::get returns Option<&F>); KpType = fn pointers, no heap/dynamic dispatch - fn where_(mut self, path: KpType<'static, T, F>, predicate: impl Fn(&F) -> bool + 'static) -> Self + fn where_( + mut self, + path: KpType<'static, T, F>, + predicate: impl Fn(&F) -> bool + 'static, + ) -> Self where F: 'static, { @@ -64,7 +68,10 @@ where // Execute and get all matching items fn all(&self) -> Vec<&T> { - self.data.iter().filter(|item| self.apply_filters(item)).collect() + self.data + .iter() + .filter(|item| self.apply_filters(item)) + .collect() } // Get first matching item @@ -74,7 +81,10 @@ where // Count matching items fn count(&self) -> usize { - self.data.iter().filter(|item| self.apply_filters(item)).count() + self.data + .iter() + .filter(|item| self.apply_filters(item)) + .count() } // Limit results @@ -99,7 +109,11 @@ where where F: Ord + 'static, { - let mut results: Vec<&T> = self.data.iter().filter(|item| self.apply_filters(item)).collect(); + let mut results: Vec<&T> = self + .data + .iter() + .filter(|item| self.apply_filters(item)) + .collect(); results.sort_by(|a, b| path.get(*a).cmp(&path.get(*b))); results } @@ -109,29 +123,45 @@ where where F: Ord + 'static, { - let mut results: Vec<&T> = self.data.iter().filter(|item| self.apply_filters(item)).collect(); + let mut results: Vec<&T> = self + .data + .iter() + .filter(|item| self.apply_filters(item)) + .collect(); results.sort_by(|a, b| path.get(*b).cmp(&path.get(*a))); results } // Order by a float field (ascending) - for f64 fn order_by_float(&self, path: KpType<'static, T, f64>) -> Vec<&'a T> { - let mut results: Vec<&T> = self.data.iter().filter(|item| self.apply_filters(item)).collect(); + let mut results: Vec<&T> = self + .data + .iter() + .filter(|item| self.apply_filters(item)) + .collect(); results.sort_by(|a, b| { let a_val = path.get(*a).copied().unwrap_or(0.0); let b_val = path.get(*b).copied().unwrap_or(0.0); - a_val.partial_cmp(&b_val).unwrap_or(std::cmp::Ordering::Equal) + a_val + .partial_cmp(&b_val) + .unwrap_or(std::cmp::Ordering::Equal) }); results } // Order by a float field (descending) - for f64 fn order_by_float_desc(&self, path: KpType<'static, T, f64>) -> Vec<&'a T> { - let mut results: Vec<&T> = self.data.iter().filter(|item| self.apply_filters(item)).collect(); + let mut results: Vec<&T> = self + .data + .iter() + .filter(|item| self.apply_filters(item)) + .collect(); results.sort_by(|a, b| { let a_val = path.get(*a).copied().unwrap_or(0.0); let b_val = path.get(*b).copied().unwrap_or(0.0); - b_val.partial_cmp(&a_val).unwrap_or(std::cmp::Ordering::Equal) + b_val + .partial_cmp(&a_val) + .unwrap_or(std::cmp::Ordering::Equal) }); results } @@ -396,20 +426,13 @@ fn main() { ); println!( " Min Price: ${:.2}", - electronics_query - .min_float(Product::price()) - .unwrap_or(0.0) + electronics_query.min_float(Product::price()).unwrap_or(0.0) ); println!( " Max Price: ${:.2}", - electronics_query - .max_float(Product::price()) - .unwrap_or(0.0) - ); - println!( - " Total Stock: {}", - electronics_query.sum(Product::stock()) + electronics_query.max_float(Product::price()).unwrap_or(0.0) ); + println!(" Total Stock: {}", electronics_query.sum(Product::stock())); // Query 6: Complex filtering with ordering println!("\n--- Query 6: Electronics Under $200, Ordered by Rating ---"); diff --git a/examples/akp_wgpu_runner.rs b/examples/akp_wgpu_runner.rs index cda132b..f81fa86 100644 --- a/examples/akp_wgpu_runner.rs +++ b/examples/akp_wgpu_runner.rs @@ -4,7 +4,9 @@ //! (Builds with key-paths-iter gpu feature enabled in dev-dependencies.) use key_paths_derive::{Akp, Kp, Pkp}; -use key_paths_iter::wgpu::{numeric_akp_f32, AKpRunner, AKpTier, GpuValue, RunResults, WgpuContext}; +use key_paths_iter::wgpu::{ + AKpRunner, AKpTier, GpuValue, RunResults, WgpuContext, numeric_akp_f32, +}; use rust_key_paths::{AKp, KpType}; #[derive(Kp, Pkp, Akp, Debug)] @@ -15,7 +17,7 @@ struct User { fn main() -> Result<(), Box> { let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), score: 42.0, }; diff --git a/examples/all_containers_test.rs b/examples/all_containers_test.rs index db7edb4..f8773f4 100644 --- a/examples/all_containers_test.rs +++ b/examples/all_containers_test.rs @@ -1,3 +1,5 @@ +use key_paths_derive::Kp; +use rust_key_paths::KpType; use std::borrow::Cow; use std::cell::{Cell, RefCell}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; @@ -5,8 +7,6 @@ use std::marker::PhantomData; use std::ops::Range; use std::rc::Rc; use std::sync::{Arc, OnceLock}; -use key_paths_derive::Kp; -use rust_key_paths::KpType; #[derive(Debug, Kp)] struct AllContainersTest { diff --git a/examples/arc_sync_derive_example.rs b/examples/arc_sync_derive_example.rs index 8fb3153..ab34684 100644 --- a/examples/arc_sync_derive_example.rs +++ b/examples/arc_sync_derive_example.rs @@ -1,6 +1,6 @@ -use std::sync::{Arc, Mutex, RwLock}; use key_paths_derive::Kp; use rust_key_paths::async_lock::SyncKeyPathLike; +use std::sync::{Arc, Mutex, RwLock}; #[derive(Kp, Clone, Debug)] struct SomeOtherStruct { @@ -36,13 +36,19 @@ fn main() { // Test Arc> field access let field1_path = SomeStruct::field1(); if let Some(field1_ref) = field1_path.get(&some_struct) { - println!("✅ Arc> field accessible: {:?}", field1_ref); + println!( + "✅ Arc> field accessible: {:?}", + field1_ref + ); } // Test Arc> field access let field2_path = SomeStruct::field2(); if let Some(field2_ref) = field2_path.get(&some_struct) { - println!("✅ Arc> field accessible: {:?}", field2_ref); + println!( + "✅ Arc> field accessible: {:?}", + field2_ref + ); } println!("\n🎯 Testing with WithContainer Trait"); @@ -145,5 +151,4 @@ fn main() { let contact_arc_path = Employee::contact(); } } - } diff --git a/examples/attribute_scopes.rs b/examples/attribute_scopes.rs index a5ce60f..49139fd 100644 --- a/examples/attribute_scopes.rs +++ b/examples/attribute_scopes.rs @@ -24,8 +24,7 @@ fn main() { let nickname_value = nickname_fr.get(&account); println!("nickname (readable): {:?}", nickname_value); - if let Some(balance_ref) = balance_w.get_mut(&mut account) - { + if let Some(balance_ref) = balance_w.get_mut(&mut account) { *balance_ref += 500; } println!("balance after writable update: {}", account.balance); diff --git a/examples/basics.rs b/examples/basics.rs index 867526e..2c1944e 100644 --- a/examples/basics.rs +++ b/examples/basics.rs @@ -3,6 +3,19 @@ //! Run with: `cargo run --example basics` use key_paths_derive::Kp; +use rust_key_paths::{Kp, KpDynamic, KpType}; + +pub struct Service { + rect_to_width_kp: KpDynamic, +} + +// impl Service { +// pub fn new() -> Self { +// Self { +// rect_to_width_kp: Rectangle::test().into(), +// } +// } +// } #[derive(Debug, Kp)] struct Size { @@ -16,6 +29,22 @@ struct Rectangle { name: String, } +// Standalone fn pointers for keypath (reference: lib.rs identity_typed / Kp with fn types) + +impl Rectangle { + // /// Keypath to `size.width`, built with fn pointers (same pattern as lib.rs `identity_typed`). + // pub const fn size() -> KpType<'static, Rectangle, Size> { + // const fn g(r: &Rectangle) -> Option<&Size> { + // Some(&r.size) + // } + // const fn s(r: &mut Rectangle) -> Option<&mut Size> { + // Some(&mut r.size) + // } + + // Kp::new_const(g, s) + // } +} + fn main() { let mut rect = Rectangle { size: Size { diff --git a/examples/basics_casepath.rs b/examples/basics_casepath.rs index 812ba1c..9e15cfc 100644 --- a/examples/basics_casepath.rs +++ b/examples/basics_casepath.rs @@ -1,24 +1,77 @@ -use std::sync::Arc; use key_paths_derive::Kp; +use parking_lot::{Mutex, RwLock}; +use std::cell::{RefCell, RefMut}; +use std::rc::Rc; +use std::sync::Arc; +// cargo check --example basics_casepath --features parking_lot +// cargo run --example basics_casepath --features parking_lot #[derive(Debug, Kp)] struct SomeComplexStruct { - scsf: Option, - scfs2: Arc>, + scsf: Option>, + identity: String, + scfs2: Arc>, + scfs3: Arc>, + // scfs4: Arc>, + // scfs5: Option>, + scfs6: Option>, + scfs7: Option>, + scfs8: Option>, + scfs9: Option>, + + scfs10: std::sync::Mutex>, + scfs11: std::sync::RwLock>, + scfs12: Mutex>, + scfs13: RwLock>, + + scfs14: Option>>, + scfs15: Option>>, + scfs16: Option>>, + scfs17: Option>>, + + // Locks inside Arc (LockKp: root SomeComplexStruct, value SomeOtherStruct) + scfs_arc_pl_m: Arc>, + scfs_arc_pl_rw: Arc>, + scfs_arc_std_mo: Arc>>, + scfs_arc_std_rwo: Arc>>, + scfs_arc_pl_mo: Arc>>, + scfs_arc_pl_rwo: Arc>>, + scfs_o_arc_std_m: Option>>, + scfs_o_arc_std_rw: Option>>, + scfs_o_arc_pl_m: Option>>, + scfs_o_arc_pl_rw: Option>>, + + // Tokio: same combinations as above — produce AsyncLockKp (root SomeComplexStruct, value SomeOtherStruct or Option) + #[cfg(feature = "tokio")] + scfs_t_arc_m: Arc>, + #[cfg(feature = "tokio")] + scfs_t_arc_rw: Arc>, + #[cfg(feature = "tokio")] + scfs_t_arc_mo: Arc>>, + #[cfg(feature = "tokio")] + scfs_t_arc_rwo: Arc>>, + #[cfg(feature = "tokio")] + scfs_t_o_arc_m: Option>>, + #[cfg(feature = "tokio")] + scfs_t_o_arc_rw: Option>>, + #[cfg(feature = "tokio")] + scfs_t_o_arc_mo: Option>>>, + #[cfg(feature = "tokio")] + scfs_t_o_arc_rwo: Option>>>, } -#[derive(Debug, Kp)] +#[derive(Debug, Kp, Clone)] struct SomeOtherStruct { - sosf: Option, + sosf: Box>, } -#[derive(Debug, Kp)] +#[derive(Debug, Clone, Kp)] enum SomeEnum { A(String), - B(Box), + B(Option>), } -#[derive(Debug, Kp)] +#[derive(Debug, Kp, Clone)] struct OneMoreStruct { omsf: Option, omse: Option, @@ -31,39 +84,128 @@ struct DarkStruct { impl SomeComplexStruct { fn new() -> Self { + let inner = SomeOtherStruct { + sosf: Box::new(None), + }; + 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")), - }))), - }), + scsf: Some(Box::new(inner.clone())), + identity: String::from("SomeComplexStruct"), + + // Arc> + scfs2: Arc::new(std::sync::Mutex::new(inner.clone())), + scfs3: Arc::new(std::sync::RwLock::new(inner.clone())), + + // Option> + scfs6: Some(std::sync::Mutex::new(inner.clone())), + scfs7: Some(std::sync::RwLock::new(inner.clone())), + + // Option> + scfs8: Some(Mutex::new(inner.clone())), + scfs9: Some(RwLock::new(inner.clone())), + + // std::sync::Mutex/RwLock> + scfs10: std::sync::Mutex::new(Some(inner.clone())), + scfs11: std::sync::RwLock::new(Some(inner.clone())), + + // parking_lot::Mutex/RwLock> + scfs12: Mutex::new(Some(inner.clone())), + scfs13: RwLock::new(Some(inner.clone())), + + // Option>> + scfs14: Some(std::sync::Mutex::new(Some(inner.clone()))), + scfs15: Some(std::sync::RwLock::new(Some(inner.clone()))), + + // Option>> + scfs16: Some(Mutex::new(Some(inner.clone()))), + scfs17: Some(RwLock::new(Some(inner.clone()))), + + // Locks inside Arc + scfs_arc_pl_m: Arc::new(Mutex::new(inner.clone())), + scfs_arc_pl_rw: Arc::new(RwLock::new(inner.clone())), + scfs_arc_std_mo: Arc::new(std::sync::Mutex::new(Some(inner.clone()))), + scfs_arc_std_rwo: Arc::new(std::sync::RwLock::new(Some(inner.clone()))), + scfs_arc_pl_mo: Arc::new(Mutex::new(Some(inner.clone()))), + scfs_arc_pl_rwo: Arc::new(RwLock::new(Some(inner.clone()))), + + // Option> + scfs_o_arc_std_m: Some(Arc::new(std::sync::Mutex::new(inner.clone()))), + scfs_o_arc_std_rw: Some(Arc::new(std::sync::RwLock::new(inner.clone()))), + scfs_o_arc_pl_m: Some(Arc::new(Mutex::new(inner.clone()))), + scfs_o_arc_pl_rw: Some(Arc::new(RwLock::new(inner))), + + // Tokio fields (only when feature is enabled) + #[cfg(feature = "tokio")] + scfs_t_arc_m: Arc::new(tokio::sync::Mutex::new(SomeOtherStruct { + sosf: Box::new(None), + })), + #[cfg(feature = "tokio")] + scfs_t_arc_rw: Arc::new(tokio::sync::RwLock::new(SomeOtherStruct { + sosf: Box::new(None), })), + #[cfg(feature = "tokio")] + scfs_t_arc_mo: Arc::new(tokio::sync::Mutex::new(Some(SomeOtherStruct { + sosf: Box::new(None), + }))), + #[cfg(feature = "tokio")] + scfs_t_arc_rwo: Arc::new(tokio::sync::RwLock::new(Some(SomeOtherStruct { + sosf: Box::new(None), + }))), + #[cfg(feature = "tokio")] + scfs_t_o_arc_m: Some(Arc::new(tokio::sync::Mutex::new(SomeOtherStruct { + sosf: Box::new(None), + }))), + #[cfg(feature = "tokio")] + scfs_t_o_arc_rw: Some(Arc::new(tokio::sync::RwLock::new(SomeOtherStruct { + sosf: Box::new(None), + }))), + #[cfg(feature = "tokio")] + scfs_t_o_arc_mo: Some(Arc::new(tokio::sync::Mutex::new(Some(SomeOtherStruct { + sosf: Box::new(None), + })))), + #[cfg(feature = "tokio")] + scfs_t_o_arc_rwo: Some(Arc::new(tokio::sync::RwLock::new(Some(SomeOtherStruct { + sosf: Box::new(None), + })))), } } } fn main() { let mut instance = SomeComplexStruct::new(); - SomeComplexStruct::scsf() + SomeComplexStruct::scfs2() .then(SomeOtherStruct::sosf()) .then(OneMoreStruct::omse()) .then(SomeEnum::b()) // Generated by Casepaths macro .then(DarkStruct::dsf()) - .get_mut(&mut instance).map(|x| { - *x = String::from("🖖🏿🖖🏿🖖🏿🖖🏿"); - }); + .get_mut(&mut instance) + .map(|x| { + *x = String::from("🖖🏿🖖🏿🖖🏿🖖🏿"); + }); + SomeComplexStruct::scfs3() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omse()) + .then(SomeEnum::b()) // Generated by Casepaths macro + .then(DarkStruct::dsf()) + .get_mut(&mut instance) + .map(|x| { + *x = String::from("🖖🏿🖖🏿🖖🏿🖖🏿"); + }); + + // LockKp for Arc>> should yield value SomeOtherStruct + let x_pl_rw: Option<&SomeOtherStruct> = SomeComplexStruct::scfs_arc_pl_rwo().get(&instance); + // Same for Arc>> + let x_std_m: Option<&SomeOtherStruct> = SomeComplexStruct::scfs_arc_std_mo().get(&instance); + // And Arc>> + let x_std_rw: Option<&SomeOtherStruct> = SomeComplexStruct::scfs_arc_std_rwo().get(&instance); + // And Arc>> + let x_pl_m: Option<&SomeOtherStruct> = SomeComplexStruct::scfs_arc_pl_mo().get(&instance); + assert!(x_pl_rw.is_some()); + assert!(x_std_m.is_some()); + assert!(x_std_rw.is_some()); + assert!(x_pl_m.is_some()); // if let Some(omsf) = SomeComplexStruct::scfs2_lock() // .then(SomeOtherStruct::sosf()) // .then(OneMoreStruct::omse()) @@ -74,7 +216,6 @@ fn main() { // *omsf = String::from("This is changed 🖖🏿🖖🏿🖖🏿🖖🏿🖖🏿🖖🏿🖖🏿"); // } - - println!("instance = {:?}", instance.scsf.unwrap().sosf.unwrap().omse.unwrap()); + // println!("instance = {:?}", instance.scsf.unwrap().sosf.unwrap().omse.unwrap()); // output - instance = B(DarkStruct { dsf: Some("🖖🏿🖖🏿🖖🏿🖖🏿") }) } diff --git a/examples/basics_macros.rs b/examples/basics_macros.rs index 99724c4..63c46c4 100644 --- a/examples/basics_macros.rs +++ b/examples/basics_macros.rs @@ -21,7 +21,7 @@ fn main() { }, name: "MyRect".into(), }; - + // Keypaths from derive-generated methods // Note: size and name are NOT Option types, so they use () methods, not _fw() let rect_size_w = Rectangle::size(); @@ -33,8 +33,7 @@ fn main() { println!("Name (readable): {:?}", name_readable.get(&rect)); let size_writable = Rectangle::size(); - if let Some(s) = size_writable.get_mut(&mut rect) - { + if let Some(s) = size_writable.get_mut(&mut rect) { s.width += 1; } diff --git a/examples/box_and_pin_example.rs b/examples/box_and_pin_example.rs index 0012ae5..fe44bc5 100644 --- a/examples/box_and_pin_example.rs +++ b/examples/box_and_pin_example.rs @@ -57,12 +57,18 @@ fn main() { // Container access: pinned() returns KpType>> let pin_container_kp = WithPin::pinned(); let container_ref = pin_container_kp.get(&with_pin).unwrap(); - println!(" pinned() -> Pin>: {:?}", Pin::as_ref(container_ref).get_ref().as_str()); + println!( + " pinned() -> Pin>: {:?}", + Pin::as_ref(container_ref).get_ref().as_str() + ); // Inner access: pinned_inner() returns KpType (String: Unpin) let pin_inner_kp = WithPin::pinned_inner(); assert_eq!(pin_inner_kp.get(&with_pin), Some(&"pinned".to_string())); - println!(" pinned_inner() -> String (requires Unpin): {:?}", pin_inner_kp.get(&with_pin)); + println!( + " pinned_inner() -> String (requires Unpin): {:?}", + pin_inner_kp.get(&with_pin) + ); let mut with_pin_mut = WithPin { pinned: Pin::new(Box::new("mutable".to_string())), @@ -70,7 +76,12 @@ fn main() { if let Some(s) = pin_inner_kp.get_mut(&mut with_pin_mut) { *s = "changed".to_string(); } - println!(" after mutation via pinned_inner: {:?}", std::pin::Pin::as_ref(&with_pin_mut.pinned).get_ref().as_str()); + println!( + " after mutation via pinned_inner: {:?}", + std::pin::Pin::as_ref(&with_pin_mut.pinned) + .get_ref() + .as_str() + ); println!(); // --- Pin> --- @@ -80,7 +91,10 @@ fn main() { }; let int_inner_kp = WithPinDirect::pinned_value_inner(); assert_eq!(int_inner_kp.get(&with_pin_int), Some(&42)); - println!(" pinned_value_inner() -> i32: {:?}", int_inner_kp.get(&with_pin_int)); + println!( + " pinned_value_inner() -> i32: {:?}", + int_inner_kp.get(&with_pin_int) + ); let mut with_pin_int_mut = WithPinDirect { pinned_value: Pin::new(Box::new(100)), diff --git a/examples/box_keypath.rs b/examples/box_keypath.rs index a781d28..94f603b 100644 --- a/examples/box_keypath.rs +++ b/examples/box_keypath.rs @@ -1,209 +1,98 @@ -use keypaths_proc::{Casepaths, Kp}; +use key_paths_derive::Kp; -#[derive(Debug, Kp)] -#[Writable] +#[derive(Debug, Kp, Default, Clone)] struct SomeComplexStruct { scsf: Box, } -impl SomeComplexStruct { - fn new() -> Self { - Self { - scsf: Box::new(SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }), - } - } -} - -#[derive(Debug, Kp)] -#[Writable] +#[derive(Debug, Kp, Default, Clone)] struct SomeOtherStruct { sosf: OneMoreStruct, } -#[derive(Debug, Casepaths)] -#[Writable] +#[derive(Debug, Kp, Clone)] enum SomeEnum { A(String), B(DarkStruct), } -#[derive(Debug, Kp)] -#[Writable] +impl Default for SomeEnum { + fn default() -> Self { + SomeEnum::A(String::new()) + } +} + +#[derive(Debug, Kp, Default, Clone)] struct OneMoreStruct { omsf: String, omse: SomeEnum, } -#[derive(Debug, Kp)] -#[Writable] +#[derive(Debug, Kp, Default, Clone)] struct DarkStruct { dsf: String, } -fn main() { - use rust_keypaths::WritableOptionalKeyPath; - - println!("=== KeyPath Display and Debug Examples ===\n"); - - // Note: These fields are NOT Option types, so we use _w() methods, not _fw() - // For Box, we manually create a keypath that unwraps the Box - // For enum variants, we use _fw() which returns WritableOptionalKeyPath - - // Build a long chain step by step, showing Display/Debug at each stage - println!("--- Step 1: Start with Box field ---"); - let step1 = SomeComplexStruct::scsf_fw(); - println!(" Display: {}", step1); - println!(" Debug: {:?}\n", step1); - - println!("--- Step 2: Chain to SomeOtherStruct field ---"); - let step2 = step1.then(SomeOtherStruct::sosf_fw()); - println!(" Display: {}", step2); - println!(" Debug: {:?}\n", step2); - - println!("--- Step 3: Chain to OneMoreStruct enum field ---"); - let step3 = step2.then(OneMoreStruct::omse_fw()); - println!(" Display: {}", step3); - println!(" Debug: {:?}\n", step3); - - println!("--- Step 4: Chain to SomeEnum::B variant ---"); - let step4 = step3.then(SomeEnum::b_fw()); - println!(" Display: {}", step4); - println!(" Debug: {:?}\n", step4); - - println!("--- Step 5: Final chain to DarkStruct field ---"); - let final_path = step4.then(DarkStruct::dsf_fw()); - println!(" Display: {}", final_path); - println!(" Debug: {:?}\n", final_path); - - // Test with different initialization scenarios - println!("=== Testing with Different Initializations ===\n"); - - // Scenario 1: Normal case - all fields present (Some) - println!("--- Scenario 1: All fields present (Some) ---"); - let mut instance1 = SomeComplexStruct::new(); - println!(" Instance: {:?}", instance1); - println!(" Final path Display: {}", final_path); - if let Some(value) = final_path.get_mut(&mut instance1) { - println!(" Result: Some({:?})", value); - *value = String::from("changed via keypath chain"); - println!(" After change: {:?}\n", instance1); - } - - // Scenario 2: Enum variant mismatch - returns None - println!("--- Scenario 2: Wrong enum variant (None) ---"); - let mut instance2 = SomeComplexStruct { - scsf: Box::new(SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("test"), - omse: SomeEnum::A(String::from("wrong variant")), // A instead of B - }, - }), - }; - println!(" Instance: {:?}", instance2); - println!(" Final path Display: {}", final_path); - match final_path.get_mut(&mut instance2) { - Some(value) => println!(" Result: Some({:?})", value), - None => println!(" Result: None (enum variant mismatch)\n"), - } - - // Scenario 3: Show intermediate None in chain - // Rebuild keypaths for checking intermediate steps - println!("--- Scenario 3: Intermediate None in chain ---"); - let mut instance3 = SomeComplexStruct { - scsf: Box::new(SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("test"), - omse: SomeEnum::A(String::from("variant A")), - }, - }), - }; - - // Check each step in the chain (rebuild keypaths for each check) - println!(" Checking step 1 (Box field):"); - let check_step1 = SomeComplexStruct::scsf_fw(); - println!(" KeyPath Display: {}", check_step1); - match check_step1.get_mut(&mut instance3) { - Some(_) => println!(" ✓ Some - Box field exists"), - None => println!(" ✗ None - Box field missing"), - } - - println!(" Checking step 2 (SomeOtherStruct field):"); - let check_step2 = SomeComplexStruct::scsf_fw().then(SomeOtherStruct::sosf_fw()); - println!(" KeyPath Display: {}", check_step2); - match check_step2.get_mut(&mut instance3) { - Some(_) => println!(" ✓ Some - Field exists"), - None => println!(" ✗ None - Field missing"), - } - - println!(" Checking step 3 (OneMoreStruct enum field):"); - let check_step3 = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()); - println!(" KeyPath Display: {}", check_step3); - match check_step3.get_mut(&mut instance3) { - Some(_) => println!(" ✓ Some - Enum field exists"), - None => println!(" ✗ None - Enum field missing"), - } - - println!(" Checking step 4 (SomeEnum::B variant):"); - let check_step4 = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_fw()); - println!(" KeyPath Display: {}", check_step4); - println!(" KeyPath Debug: {:?}", check_step4); - println!(" KeyPath Debug (detailed):"); - println!(" {:#?}", check_step4); - match check_step4.trace_chain(&mut instance3) { - Ok(()) => println!(" ✓ Some - Variant B exists"), - Err(msg) => println!(" ✗ Chain broken: {}", msg), - } - println!(); - - println!(" Checking final path:"); - let check_final = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_fw()) - .then(DarkStruct::dsf_fw()); - println!(" KeyPath Display: {}", check_final); - println!(" KeyPath Debug: {:?}", check_final); - println!(" KeyPath Debug (detailed):"); - println!(" {:#?}", check_final); - match check_final.trace_chain(&mut instance3) { - Ok(()) => println!(" ✓ Some - Full chain successful"), - Err(msg) => println!(" ✗ Chain broken: {}", msg), - } - println!(); - - // Scenario 4: Create alternative chain for variant A - println!("--- Scenario 4: Alternative chain for SomeEnum::A ---"); - let alt_path = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::a_fw()); - println!(" Alternative path Display: {}", alt_path); - println!(" Alternative path Debug: {:?}", alt_path); - match alt_path.get_mut(&mut instance3) { - Some(value) => { - println!(" Result: Some({:?})", value); - *value = String::from("changed via alternative path"); - println!(" After change: {:?}\n", instance3); - } - None => println!(" Result: None\n"), - } +/// Build a fully initialized `SomeComplexStruct` by mutating via keypaths only +/// (no direct struct literals for nested data — tests keypath traversal through Box). +fn init_via_keypaths() -> SomeComplexStruct { + let mut root = SomeComplexStruct::default(); + + // kp: root -> scsf (Box) -> sosf -> omsf (String) + SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omsf()) + .get_mut(&mut root) + .map(|s| *s = "omsf_value".to_string()); + + // kp: root -> scsf -> sosf -> omse (SomeEnum); set variant to B + SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omse()) + .get_mut(&mut root) + .map(|e| *e = SomeEnum::B(DarkStruct::default())); + + // kp: root -> scsf -> sosf -> omse -> B payload -> dsf + SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omse()) + .then(SomeEnum::b()) + .then(DarkStruct::dsf()) + .get_mut(&mut root) + .map(|s| *s = "dark_value".to_string()); + + root +} - println!("=== Summary ==="); - println!("- Display shows: KeyPath type and type information"); - println!("- Debug shows: Same as Display for consistent formatting"); - println!("- Long chains: Each step can be inspected independently"); - println!("- None cases: Display still works, shows the full chain structure"); - println!("- Some cases: Display shows the complete path that succeeded"); +fn main() { + let instance = init_via_keypaths(); + + // Read back via same keypaths to verify + let omsf = SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omsf()) + .get(&instance); + assert_eq!(omsf, Some(&"omsf_value".to_string())); + + let dsf = SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omse()) + .then(SomeEnum::b()) + .then(DarkStruct::dsf()) + .get(&instance); + assert_eq!(dsf, Some(&"dark_value".to_string())); + + /* + Kp - struct 8 genric + KpType - typealias 2 genric + KpTrait - to enforce use to use kp + KpTraitType - 2 gen + Accessor - acccessor fns into a triat - get, get_mut + OptionalAccessor - get_optionl, get_mut_optional + HofExt - map, filter, flatmap ..... + Chain - then + CoercionTrait - to_box, to_arc + ChainExt - then_async, then_pin_fut, then_sync + */ + println!("{:?}", instance); } diff --git a/examples/builder_pattern.rs b/examples/builder_pattern.rs new file mode 100644 index 0000000..703ec0f --- /dev/null +++ b/examples/builder_pattern.rs @@ -0,0 +1,90 @@ +use key_paths_derive::Kp; +use rust_key_paths::{Kp, KpType}; + +#[derive(Debug, Kp, Default, Clone)] +struct SomeComplexStruct { + scsf: Box, +} + +#[derive(Debug, Kp, Default, Clone)] +struct SomeOtherStruct { + sosf: OneMoreStruct, +} + +#[derive(Debug, Kp, Clone)] +enum SomeEnum { + A(String), + B(DarkStruct), +} + +impl Default for SomeEnum { + fn default() -> Self { + SomeEnum::A(String::new()) + } +} + +#[derive(Debug, Kp, Default, Clone)] +struct OneMoreStruct { + omsf: String, + omse: SomeEnum, +} + +#[derive(Debug, Kp, Default, Clone)] +struct DarkStruct { + dsf: String, +} + +/// Keypath builder +pub struct KpBuilder { + value: T, +} + +impl KpBuilder { + pub fn new() -> Self { + Self { + value: T::default(), + } + } + + pub fn set(mut self, kp: impl Fn(&mut T) -> Option<&mut V>, value: V) -> Self + where + V: Clone, + { + if let Some(field) = kp(&mut self.value) { + *field = value; + } + self + } + + pub fn build(self) -> T { + self.value + } +} + +fn main() { + // Method 1: Direct builder + let instance = KpBuilder::new() + .set( + |root: &mut SomeComplexStruct| { + SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omsf()) + .get_mut(root) + }, + "Hello World".to_string(), + ) + .set( + |root: &mut SomeComplexStruct| { + SomeComplexStruct::scsf() + .then(SomeOtherStruct::sosf()) + .then(OneMoreStruct::omse()) + .then(SomeEnum::b()) + .then(DarkStruct::dsf()) + .get_mut(root) + }, + "🖖🏿🖖🏿🖖🏿".to_string(), + ) + .build(); + + println!("{:#?}", instance); +} diff --git a/examples/chunk_size_example.rs b/examples/chunk_size_example.rs index 53cd67b..472e2c3 100644 --- a/examples/chunk_size_example.rs +++ b/examples/chunk_size_example.rs @@ -26,7 +26,10 @@ fn main() { let chunk = ChunkSizeOptimizer::auto_detect(&data, 500, |&x| { let _ = x + 1; }); - println!("auto_detect (cheap work, sample 500): chunk_size = {}", chunk); + println!( + "auto_detect (cheap work, sample 500): chunk_size = {}", + chunk + ); // Use a chosen chunk size in par_chunks let chunk_size = ChunkSizeOptimizer::uniform(data.len(), n); diff --git a/examples/complete_containers_no_clone_example.rs b/examples/complete_containers_no_clone_example.rs index a5acddd..8a9f076 100644 --- a/examples/complete_containers_no_clone_example.rs +++ b/examples/complete_containers_no_clone_example.rs @@ -5,63 +5,63 @@ // use std::cell::RefCell; // use std::rc::Rc; // use std::sync::{Arc, Mutex, RwLock}; -// +// // #[derive(Debug, Clone)] // struct User { // name: String, // age: u32, // email: Option, // } -// +// // fn main() { // println!("=== Complete Containers No-Clone Example ===\n"); -// +// // // Create test data // let user = User { // name: "Akash".to_string(), // age: 30, // email: Some("akash@example.com".to_string()), // }; -// +// // // Create keypaths // let name_path = KeyPath::new(|u: &User| &u.name); // let age_path = KeyPath::new(|u: &User| &u.age); // let email_path = OptionalKeyPath::new(|u: &User| u.email.as_ref()); // let name_path_w = WritableKeyPath::new(|u: &mut User| &mut u.name); // let age_path_w = WritableKeyPath::new(|u: &mut User| &mut u.age); -// +// // // ===== Example 1: Arc (Read-only) ===== // println!("--- Example 1: Arc (Read-only) ---"); -// +// // let arc_user = Arc::new(user.clone()); // name_path.clone().with_arc(&arc_user, |name| { // println!(" Name from Arc: {}", name); // }); -// +// // // ===== Example 2: Box (Read and Write) ===== // println!("--- Example 2: Box (Read and Write) ---"); -// +// // let mut boxed_user = Box::new(user.clone()); // name_path.clone().with_box(&boxed_user, |name| { // println!(" Name from Box: {}", name); // }); -// +// // name_path_w.clone().with_box_mut(&mut boxed_user, |name| { // *name = "Akash Boxed".to_string(); // println!(" Updated name in Box: {}", name); // }); -// +// // // ===== Example 3: Rc (Read-only) ===== // println!("--- Example 3: Rc (Read-only) ---"); -// +// // let rc_user = Rc::new(user.clone()); // name_path.clone().with_rc(&rc_user, |name| { // println!(" Name from Rc: {}", name); // }); -// +// // // ===== Example 4: Result (Read and Write) ===== // println!("--- Example 4: Result (Read and Write) ---"); -// +// // let mut result_user: Result = Ok(user.clone()); // if let Some(name) = name_path // .clone() @@ -69,7 +69,7 @@ // { // println!(" Name from Result: {}", name); // } -// +// // if let Some(()) = name_path_w // .clone() // .with_result_mut(&mut result_user, |name| { @@ -79,7 +79,7 @@ // { // println!(" Successfully updated Result"); // } -// +// // // Test with Err Result // let err_result: Result = Err("User not found".to_string()); // if name_path @@ -89,10 +89,10 @@ // { // println!(" Correctly handled Err Result"); // } -// +// // // ===== Example 5: Option (Read and Write) ===== // println!("--- Example 5: Option (Read and Write) ---"); -// +// // let mut option_user: Option = Some(user.clone()); // if let Some(name) = name_path // .clone() @@ -100,7 +100,7 @@ // { // println!(" Name from Option: {}", name); // } -// +// // if let Some(()) = name_path_w // .clone() // .with_option_mut(&mut option_user, |name| { @@ -110,7 +110,7 @@ // { // println!(" Successfully updated Option"); // } -// +// // // Test with None Option // let none_option: Option = None; // if name_path @@ -120,10 +120,10 @@ // { // println!(" Correctly handled None Option"); // } -// +// // // ===== Example 6: RefCell (Read and Write) ===== // println!("--- Example 6: RefCell (Read and Write) ---"); -// +// // let refcell_user = RefCell::new(user.clone()); // if let Some(name) = name_path // .clone() @@ -131,22 +131,22 @@ // { // println!(" Name from RefCell: {}", name); // } -// +// // if let Some(()) = name_path_w.clone().with_refcell_mut(&refcell_user, |name| { // *name = "Akash RefCell".to_string(); // println!(" Updated name in RefCell: {}", name); // }) { // println!(" Successfully updated RefCell"); // } -// +// // // ===== Example 7: Mutex (Read and Write) ===== // println!("--- Example 7: Mutex (Read and Write) ---"); -// +// // let mutex_user = Mutex::new(user.clone()); // name_path.clone().with_mutex(&mutex_user, |name| { // println!(" Name from Mutex: {}", name); // }); -// +// // let mut mutex_user_mut = Mutex::new(user.clone()); // name_path_w // .clone() @@ -154,15 +154,15 @@ // *name = "Akash Mutexed".to_string(); // println!(" Updated name in Mutex: {}", name); // }); -// +// // // ===== Example 8: RwLock (Read and Write) ===== // println!("--- Example 8: RwLock (Read and Write) ---"); -// +// // let rwlock_user = RwLock::new(user.clone()); // name_path.clone().with_rwlock(&rwlock_user, |name| { // println!(" Name from RwLock: {}", name); // }); -// +// // let mut rwlock_user_mut = RwLock::new(user.clone()); // age_path_w // .clone() @@ -170,10 +170,10 @@ // *age += 1; // println!(" Updated age in RwLock: {}", age); // }); -// +// // // ===== Example 9: Collection Processing (No Clone) ===== // println!("--- Example 9: Collection Processing (No Clone) ---"); -// +// // let arc_users: Vec> = vec![ // Arc::new(User { // name: "Bob".to_string(), @@ -186,7 +186,7 @@ // email: None, // }), // ]; -// +// // // Process names from Arc collection - no cloning! // let mut names = Vec::new(); // for arc_user in &arc_users { @@ -195,10 +195,10 @@ // }); // } // println!(" User names from Arc collection: {:?}", names); -// +// // // ===== Example 10: Mixed Container Types ===== // println!("--- Example 10: Mixed Container Types ---"); -// +// // let mixed_containers = vec![ // ( // "Arc", @@ -221,7 +221,7 @@ // Box::new(RefCell::new(user.clone())) as Box, // ), // ]; -// +// // println!( // " Created mixed container collection with {} types:", // mixed_containers.len() @@ -229,10 +229,10 @@ // for (name, _) in &mixed_containers { // println!(" - {}", name); // } -// +// // // ===== Example 11: Error Handling ===== // println!("--- Example 11: Error Handling ---"); -// +// // // Test with poisoned Mutex // let poisoned_mutex = Mutex::new(user.clone()); // { @@ -242,7 +242,7 @@ // }) // .ok(); // } -// +// // if name_path // .clone() // .with_mutex(&poisoned_mutex, |name| name.clone()) @@ -252,7 +252,7 @@ // } else { // println!(" Failed to access poisoned Mutex (as expected)"); // } -// +// // // Test with Err Result // let err_result: Result = Err("Database error".to_string()); // if name_path @@ -262,7 +262,7 @@ // { // println!(" Correctly handled Err Result"); // } -// +// // // Test with None Option // let none_option: Option = None; // if name_path @@ -272,7 +272,7 @@ // { // println!(" Correctly handled None Option"); // } -// +// // // Test with RefCell borrow failure // let refcell_user = RefCell::new(user.clone()); // let _borrow = refcell_user.borrow(); // Hold a borrow @@ -283,10 +283,10 @@ // { // println!(" Correctly handled RefCell borrow failure"); // } -// +// // println!("=== All Examples Completed Successfully! ==="); // } fn main() { println!("Hello, world!"); -} \ No newline at end of file +} diff --git a/examples/comprehensive_test_suite.rs b/examples/comprehensive_test_suite.rs index 3974533..82292b1 100644 --- a/examples/comprehensive_test_suite.rs +++ b/examples/comprehensive_test_suite.rs @@ -1,7 +1,7 @@ +use key_paths_derive::Kp; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; use std::rc::Rc; use std::sync::Arc; -use key_paths_derive::Kp; #[derive(Debug, Kp)] struct ComprehensiveTest { diff --git a/examples/deadlock_prevention_async.rs b/examples/deadlock_prevention_async.rs index 9f422b8..b765aac 100644 --- a/examples/deadlock_prevention_async.rs +++ b/examples/deadlock_prevention_async.rs @@ -11,7 +11,7 @@ use key_paths_derive::Kp; use std::sync::Arc; -use tokio::time::{sleep, Duration}; +use tokio::time::{Duration, sleep}; #[derive(Clone, Kp)] struct Account { @@ -143,14 +143,8 @@ async fn main() { let bank5 = bank.clone(); let handle5 = tokio::spawn(async move { - let b1 = Bank::account1_async() - .get(&*bank5) - .await - .map(|a| a.balance); - let b2 = Bank::account2_async() - .get(&*bank5) - .await - .map(|a| a.balance); + let b1 = Bank::account1_async().get(&*bank5).await.map(|a| a.balance); + let b2 = Bank::account2_async().get(&*bank5).await.map(|a| a.balance); if let (Some(b1), Some(b2)) = (b1, b2) { println!(" Task 5: Snapshot acc1={}, acc2={}", b1, b2); Bank::account2_async() diff --git a/examples/deadlock_prevention_sync.rs b/examples/deadlock_prevention_sync.rs index eb3f34b..f107b32 100644 --- a/examples/deadlock_prevention_sync.rs +++ b/examples/deadlock_prevention_sync.rs @@ -57,7 +57,10 @@ fn main() { .set(&*bank1, |acc| acc.balance += balance1) .unwrap(); let acc2_bal = Bank::account2_lock().get(&*bank1).unwrap().balance; - println!(" Thread 1: Transfer 1->2 done, acc2.balance = {}", acc2_bal); + println!( + " Thread 1: Transfer 1->2 done, acc2.balance = {}", + acc2_bal + ); }); let handle2 = thread::spawn(move || { @@ -70,7 +73,10 @@ fn main() { .set(&*bank2, |acc| acc.balance += balance2) .unwrap(); let acc1_bal = Bank::account1_lock().get(&*bank2).unwrap().balance; - println!(" Thread 2: Transfer 2->1 done, acc1.balance = {}", acc1_bal); + println!( + " Thread 2: Transfer 2->1 done, acc1.balance = {}", + acc1_bal + ); }); handle1.join().unwrap(); @@ -87,11 +93,19 @@ fn main() { let id1 = Bank::account1_lock().get(&*bank3).unwrap().id; let id2 = Bank::account2_lock().get(&*bank3).unwrap().id; if id1 < id2 { - Bank::account1_lock().set(&*bank3, |acc| acc.balance += 10).unwrap(); - Bank::account2_lock().set(&*bank3, |acc| acc.balance -= 10).unwrap(); + Bank::account1_lock() + .set(&*bank3, |acc| acc.balance += 10) + .unwrap(); + Bank::account2_lock() + .set(&*bank3, |acc| acc.balance -= 10) + .unwrap(); } else { - Bank::account2_lock().set(&*bank3, |acc| acc.balance -= 10).unwrap(); - Bank::account1_lock().set(&*bank3, |acc| acc.balance += 10).unwrap(); + Bank::account2_lock() + .set(&*bank3, |acc| acc.balance -= 10) + .unwrap(); + Bank::account1_lock() + .set(&*bank3, |acc| acc.balance += 10) + .unwrap(); } println!(" Thread 3: Ordered transfer done"); }); @@ -100,11 +114,19 @@ fn main() { let id1 = Bank::account1_lock().get(&*bank4).unwrap().id; let id2 = Bank::account2_lock().get(&*bank4).unwrap().id; if id1 < id2 { - Bank::account1_lock().set(&*bank4, |acc| acc.balance -= 5).unwrap(); - Bank::account2_lock().set(&*bank4, |acc| acc.balance += 5).unwrap(); + Bank::account1_lock() + .set(&*bank4, |acc| acc.balance -= 5) + .unwrap(); + Bank::account2_lock() + .set(&*bank4, |acc| acc.balance += 5) + .unwrap(); } else { - Bank::account2_lock().set(&*bank4, |acc| acc.balance += 5).unwrap(); - Bank::account1_lock().set(&*bank4, |acc| acc.balance -= 5).unwrap(); + Bank::account2_lock() + .set(&*bank4, |acc| acc.balance += 5) + .unwrap(); + Bank::account1_lock() + .set(&*bank4, |acc| acc.balance -= 5) + .unwrap(); } println!(" Thread 4: Ordered transfer done"); }); diff --git a/examples/drop_test.rs b/examples/drop_test.rs index 008f26e..31c0b25 100644 --- a/examples/drop_test.rs +++ b/examples/drop_test.rs @@ -1,12 +1,11 @@ +use key_paths_derive::Kp; +use rust_key_paths::KpType; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; use std::rc::Rc; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use key_paths_derive::Kp; -use rust_key_paths::KpType; +use std::sync::atomic::{AtomicUsize, Ordering}; // cargo run --example drop_test 2>&1 - /// Increments a shared counter when dropped. Used to verify all fields are dropped after kp get/set. #[derive(Clone, Debug)] struct DropToken(Arc); diff --git a/examples/key_paths_iter_derive.rs b/examples/key_paths_iter_derive.rs index c888c72..f4a245b 100644 --- a/examples/key_paths_iter_derive.rs +++ b/examples/key_paths_iter_derive.rs @@ -25,7 +25,7 @@ fn main() { name: "Acme Corp".to_string(), employees: vec![ Employee { - name: "Alice".to_string(), + name: "Akash".to_string(), role: "Engineer".to_string(), active: true, years: 5, diff --git a/examples/key_paths_iter_par_derive.rs b/examples/key_paths_iter_par_derive.rs index 6ff4209..8e03df9 100644 --- a/examples/key_paths_iter_par_derive.rs +++ b/examples/key_paths_iter_par_derive.rs @@ -44,7 +44,11 @@ fn main() { let employees_kp = Company::employees(); println!("=== key-paths-iter parallel (Kp derive) ===\n"); - println!("Company: {} ({} employees)\n", company.name, company.employees.len()); + println!( + "Company: {} ({} employees)\n", + company.name, + company.employees.len() + ); // Parallel map: extract salaries let salaries = employees_kp.par_map(&company, |e| e.salary); @@ -56,21 +60,38 @@ fn main() { // Parallel count by predicate let high_earners = employees_kp.par_count_by(&company, |e| e.salary >= 100_000); - println!("High earners (salary >= 100k): {} (par_count_by)", high_earners); + println!( + "High earners (salary >= 100k): {} (par_count_by)", + high_earners + ); // Parallel any / all let has_manager = employees_kp.par_any(&company, |e| e.role == "Manager"); let all_have_salary = employees_kp.par_all(&company, |e| e.salary > 0); - println!("Has manager: {}, all have salary: {} (par_any, par_all)", has_manager, all_have_salary); + println!( + "Has manager: {}, all have salary: {} (par_any, par_all)", + has_manager, all_have_salary + ); // Parallel min/max by key - let min_sal = employees_kp.par_min_by_key(&company, |e| e.salary).map(|e| e.salary); - let max_sal = employees_kp.par_max_by_key(&company, |e| e.salary).map(|e| e.salary); - println!("Min salary: {:?}, max salary: {:?} (par_min_by_key, par_max_by_key)", min_sal, max_sal); + let min_sal = employees_kp + .par_min_by_key(&company, |e| e.salary) + .map(|e| e.salary); + let max_sal = employees_kp + .par_max_by_key(&company, |e| e.salary) + .map(|e| e.salary); + println!( + "Min salary: {:?}, max salary: {:?} (par_min_by_key, par_max_by_key)", + min_sal, max_sal + ); // Parallel partition: active vs inactive let (active, inactive) = employees_kp.par_partition(&company, |e| e.active); - println!("Active: {}, inactive: {} (par_partition)", active.len(), inactive.len()); + println!( + "Active: {}, inactive: {} (par_partition)", + active.len(), + inactive.len() + ); // Parallel group by role let by_role = employees_kp.par_group_by(&company, |e| e.role.clone()); @@ -81,9 +102,15 @@ fn main() { // Parallel sort by key (returns owned Vec) let sorted_by_salary = employees_kp.par_sort_by_key(&company, |e| e.salary); - println!("\nSorted by salary (par_sort_by_key): {} items", sorted_by_salary.len()); + println!( + "\nSorted by salary (par_sort_by_key): {} items", + sorted_by_salary.len() + ); if let (Some(first), Some(last)) = (sorted_by_salary.first(), sorted_by_salary.last()) { - println!(" First: {} ({}), last: {} ({})", first.name, first.salary, last.name, last.salary); + println!( + " First: {} ({}), last: {} ({})", + first.name, first.salary, last.name, last.salary + ); } println!("\nDone."); diff --git a/examples/kp_derive_showcase.rs b/examples/kp_derive_showcase.rs index bee0e0e..875d380 100644 --- a/examples/kp_derive_showcase.rs +++ b/examples/kp_derive_showcase.rs @@ -32,7 +32,7 @@ fn main() { name: "Tech Corp".to_string(), employees: vec![ Person { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, email: Some("alice@example.com".to_string()), address: Box::new(Address { diff --git a/examples/kp_gpu_example.rs b/examples/kp_gpu_example.rs index 0b946a6..31faf9b 100644 --- a/examples/kp_gpu_example.rs +++ b/examples/kp_gpu_example.rs @@ -36,15 +36,22 @@ fn main() -> Result<(), Box> { println!("score * 2 + 1 = {:?}", result); // Some(85.0) // Chain with .and_then_gpu() - let gpu_score2 = User::score().map_gpu("output[id] = input[id] * 2.0;") + let gpu_score2 = User::score() + .map_gpu("output[id] = input[id] * 2.0;") .and_then_gpu("output[id] = output[id] + 1.0;"); let result2 = gpu_score2.run_one(&user, &ctx); println!("(score * 2) + 1 = {:?}", result2); // .par_gpu: one call to attach kernel and run over a slice let users = vec![ - User { score: 1.0, age: 20 }, - User { score: 2.0, age: 25 }, + User { + score: 1.0, + age: 20, + }, + User { + score: 2.0, + age: 25, + }, ]; let results: Vec> = User::score().par_gpu("output[id] = input[id] * 3.0;", &users, &ctx); diff --git a/examples/kp_gpu_practical_app.rs b/examples/kp_gpu_practical_app.rs index 6a72f6f..bce9a9a 100644 --- a/examples/kp_gpu_practical_app.rs +++ b/examples/kp_gpu_practical_app.rs @@ -64,8 +64,7 @@ fn main() -> Result<(), Box> { let mc_option = MonteCarloOption { payoffs }; - let gpu_payoffs = MonteCarloOption::payoffs() - .map_gpu_vec("output[id] = max(0.0, input[id]);"); // ensure non-negative payoffs + let gpu_payoffs = MonteCarloOption::payoffs().map_gpu_vec("output[id] = max(0.0, input[id]);"); // ensure non-negative payoffs let t0 = Instant::now(); let capped = gpu_payoffs.run_one(&mc_option, &ctx).unwrap_or_default(); @@ -78,7 +77,10 @@ fn main() -> Result<(), Box> { }; println!("1. Monte Carlo option ({} paths)", N_PATHS); - println!(" GPU: max(0, payoff) over all paths → option value = {:.6}", option_value); + println!( + " GPU: max(0, payoff) over all paths → option value = {:.6}", + option_value + ); println!(" Dispatch + readback: {:.2} ms\n", gpu_ms); // ─── 2. Batch of options: intrinsic value for many contracts ─────────────── @@ -95,8 +97,7 @@ fn main() -> Result<(), Box> { }) .collect(); - let gpu_intrinsic = - OptionContract::intrinsic().map_gpu("output[id] = max(0.0, input[id]);"); + let gpu_intrinsic = OptionContract::intrinsic().map_gpu("output[id] = max(0.0, input[id]);"); let t1 = Instant::now(); let intrinsics: Vec> = gpu_intrinsic.run_many(&options, &ctx); let batch_ms = t1.elapsed().as_secs_f64() * 1000.0; @@ -106,7 +107,10 @@ fn main() -> Result<(), Box> { println!("2. Batch option intrinsics ({} contracts)", N_OPTIONS); println!(" One GPU dispatch: max(0, spot - strike) for all"); - println!(" Total intrinsic: {:.2}, ITM count: {}", total_intrinsic, in_the_money); + println!( + " Total intrinsic: {:.2}, ITM count: {}", + total_intrinsic, in_the_money + ); println!(" Time: {:.2} ms\n", batch_ms); // ─── 3. Portfolio stress: factor sensitivities × shock ───────────────────── @@ -119,10 +123,8 @@ fn main() -> Result<(), Box> { .collect(); let portfolio = RiskPosition { sensitivities }; - let gpu_stress = RiskPosition::sensitivities().map_gpu_vec(format!( - "output[id] = input[id] * {:.1};", - STRESS_FACTOR - )); + let gpu_stress = RiskPosition::sensitivities() + .map_gpu_vec(format!("output[id] = input[id] * {:.1};", STRESS_FACTOR)); let t2 = Instant::now(); let stressed = gpu_stress.run_one(&portfolio, &ctx).unwrap_or_default(); @@ -130,14 +132,25 @@ fn main() -> Result<(), Box> { let total_stressed_pnl: f32 = stressed.iter().sum(); - println!("3. Portfolio stress test ({} positions × {} factors = {} values)", - N_POSITIONS, N_FACTORS, N_POSITIONS * N_FACTORS); - println!(" GPU: sensitivities × {:.1} in one dispatch", STRESS_FACTOR); + println!( + "3. Portfolio stress test ({} positions × {} factors = {} values)", + N_POSITIONS, + N_FACTORS, + N_POSITIONS * N_FACTORS + ); + println!( + " GPU: sensitivities × {:.1} in one dispatch", + STRESS_FACTOR + ); println!(" Total stressed PnL (sum): {:.2}", total_stressed_pnl); println!(" Time: {:.2} ms\n", stress_ms); - println!("═══ Done: GPU handled {} + {} + {} elements in 3 dispatches ═══", - N_PATHS, N_OPTIONS, N_POSITIONS * N_FACTORS); + println!( + "═══ Done: GPU handled {} + {} + {} elements in 3 dispatches ═══", + N_PATHS, + N_OPTIONS, + N_POSITIONS * N_FACTORS + ); Ok(()) } diff --git a/examples/kp_gpu_vec_example.rs b/examples/kp_gpu_vec_example.rs index 228f54c..cec91ca 100644 --- a/examples/kp_gpu_vec_example.rs +++ b/examples/kp_gpu_vec_example.rs @@ -42,7 +42,10 @@ fn main() -> Result<(), Box> { // In-place: run kernel and write result back into model.weights let applied = gpu_weights.apply_one(&mut model, &ctx); - println!("apply_one: {}; model.weights = {:?}", applied, model.weights); + println!( + "apply_one: {}; model.weights = {:?}", + applied, model.weights + ); // Chain kernels with .and_then_gpu let model2 = Model { diff --git a/examples/kp_pkp_wgpu.rs b/examples/kp_pkp_wgpu.rs index 6b93177..33e5811 100644 --- a/examples/kp_pkp_wgpu.rs +++ b/examples/kp_pkp_wgpu.rs @@ -7,7 +7,7 @@ //! Run with: `cargo run --example kp_pkp_wgpu` use key_paths_derive::{Akp, Kp, Pkp}; -use key_paths_iter::wgpu::{IntoNumericAKp, AKpRunner, AKpTier, GpuValue, RunResults, WgpuContext}; +use key_paths_iter::wgpu::{AKpRunner, AKpTier, GpuValue, IntoNumericAKp, RunResults, WgpuContext}; use rust_key_paths::{AKp, KpType}; #[derive(Kp, Pkp, Akp, Debug)] @@ -18,13 +18,12 @@ struct User { fn main() -> Result<(), Box> { let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), score: 42.0, }; // Numeric: typed Kp from derive → into_numeric_akp (uses get by reference; only f32 is copied) - let score_tier = - AKpTier::Numeric(User::score().into_numeric_akp("input * 2.0 + 1.0")); + let score_tier = AKpTier::Numeric(User::score().into_numeric_akp("input * 2.0 + 1.0")); // Functional API: Kp::map takes a reference (no copy of value in the get path) let score_kp = User::score(); diff --git a/examples/kp_zip_example.rs b/examples/kp_zip_example.rs new file mode 100644 index 0000000..620577c --- /dev/null +++ b/examples/kp_zip_example.rs @@ -0,0 +1,68 @@ +//! Keypath `zip`, `zip_with`, and `zip_with_kp!`: combine keypaths on the same root. +//! +//! Like [Option::zip](Option::zip): get returns `Some((a, b))` when both keypaths succeed. +//! `zip_with` applies a transform to the pair. The macro `zip_with_kp!` zips 2–6 keypaths +//! and runs a closure on the tuple in one step. +//! +//! Run with: `cargo run --example kp_zip_example` + +use rust_key_paths::{Kp, KpType, zip_with_kp}; + +#[derive(Debug)] +struct User { + name: String, + age: u32, + city: String, +} + +fn main() { + let user = User { + name: "Akash".to_string(), + age: 30, + city: "NYC".to_string(), + }; + + // Keypaths for zip / zip_with (consumed by .zip() and .zip_with()) + let name_kp: KpType = + Kp::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); + let age_kp: KpType = + Kp::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); + + // zip: access both fields at once + let zipped = name_kp.zip(age_kp); + let (name, age) = zipped.get(&user).unwrap(); + assert_eq!(*name, "Akash"); + assert_eq!(*age, 30); + println!("zip: name = {}, age = {}", name, age); + + // zip_with: combine into a single value + let name_kp2: KpType = + Kp::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); + let age_kp2: KpType = + Kp::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); + let full_info = name_kp2.zip_with(age_kp2, |name: &String, age: &u32| { + format!("{} (age {})", name, age) + }); + let info = full_info.get(&user).unwrap(); + assert_eq!(info, "Akash (age 30)"); + println!("zip_with: {}", info); + + // zip_with_kp!: multi-field aggregation (2, 3, or more keypaths) + let name_kp_m: KpType = + Kp::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); + let age_kp_m: KpType = + Kp::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); + let city_kp: KpType = + Kp::new(|u: &User| Some(&u.city), |u: &mut User| Some(&mut u.city)); + let summary = zip_with_kp!( + &user, + |(name, age, city)| format!("{}, {} from {}", name, age, city) => + name_kp_m, + age_kp_m, + city_kp + ); + assert_eq!(summary, Some("Akash, 30 from NYC".to_string())); + println!("zip_with_kp!: {}", summary.as_deref().unwrap()); + + println!("kp_zip_example OK"); +} diff --git a/examples/memory_optimized_example.rs b/examples/memory_optimized_example.rs index df5bc5d..8cc9ef6 100644 --- a/examples/memory_optimized_example.rs +++ b/examples/memory_optimized_example.rs @@ -24,7 +24,11 @@ fn main() { // Slice of u8 let data_u8: Vec = (0..10_000_u32).map(|i| i as u8).collect(); let l1_u8 = MemoryOptimizedConfig::l1_cache_friendly(&data_u8); - println!("For u8 (size {}): l1_cache_friendly() = {} items\n", std::mem::size_of::(), l1_u8); + println!( + "For u8 (size {}): l1_cache_friendly() = {} items\n", + std::mem::size_of::(), + l1_u8 + ); // Use L2-friendly chunks for a parallel sum let chunk = MemoryOptimizedConfig::l2_cache_friendly(&data_u64); @@ -33,6 +37,10 @@ fn main() { .par_chunks(chunk) .map(|c| c.iter().copied().sum::()) .sum(); - println!("par_chunks(l2_cache_friendly) sum = {}, elapsed = {:?}", sum, start.elapsed()); + println!( + "par_chunks(l2_cache_friendly) sum = {}, elapsed = {:?}", + sum, + start.elapsed() + ); println!("\nDone."); } diff --git a/examples/optimization_guide_example.rs b/examples/optimization_guide_example.rs index 06e1297..be5f82f 100644 --- a/examples/optimization_guide_example.rs +++ b/examples/optimization_guide_example.rs @@ -13,7 +13,10 @@ fn main() { // Run presets that use 2MB stack and depth-first to avoid overflow in debug builds for (name, pool) in [ ("data_pipeline", OptimizationGuide::data_pipeline()), - ("scientific_computing", OptimizationGuide::scientific_computing()), + ( + "scientific_computing", + OptimizationGuide::scientific_computing(), + ), ("machine_learning", OptimizationGuide::machine_learning()), ] { let start = Instant::now(); diff --git a/examples/pain001_pipeline.rs b/examples/pain001_pipeline.rs index 85d001c..210dea9 100644 --- a/examples/pain001_pipeline.rs +++ b/examples/pain001_pipeline.rs @@ -9,7 +9,7 @@ use key_paths_iter::query_par::ParallelCollectionKeyPath; use rayon::prelude::*; -use rust_key_paths::{Kp, KpType, PKp, AKp}; +use rust_key_paths::{AKp, Kp, KpType, PKp}; // ══════════════════════════════════════════════════════════════════════════ // 1. PAIN.001 STRUCTS (simplified ISO 20022 pain.001.001.07) @@ -94,7 +94,8 @@ fn pain_payment_informations() -> KpType<'static, Pain001, Vec (for nested pipeline or GPU buffer extraction) #[allow(dead_code)] -fn pmt_inf_credit_transfer_tx_infos() -> KpType<'static, PaymentInformation, Vec> { +fn pmt_inf_credit_transfer_tx_infos() +-> KpType<'static, PaymentInformation, Vec> { Kp::new( |p: &PaymentInformation| Some(&p.credit_transfer_tx_infos), |p: &mut PaymentInformation| Some(&mut p.credit_transfer_tx_infos), @@ -163,7 +164,10 @@ fn total_instructed_amount(pain: &Pain001) -> f64 { fn validate_all_amounts_positive(pain: &Pain001) -> bool { let kp = pain_payment_informations(); kp.par_flat_map(pain, |pmt| { - pmt.credit_transfer_tx_infos.iter().map(|tx| tx.amount).collect::>() + pmt.credit_transfer_tx_infos + .iter() + .map(|tx| tx.amount) + .collect::>() }) .par_iter() .all(|&a| a > 0.0) @@ -201,7 +205,11 @@ fn run_akp_pipeline(pain: &Pain001) { // Filter: message_id non-empty (Root = Pain001, Value = String) let valid_akp = msg_id_akp.filter::(|s| !s.is_empty()); // get_as returns Option> when root type matches - if valid_akp.get_as::(pain).and_then(|o| o).is_some() { + if valid_akp + .get_as::(pain) + .and_then(|o| o) + .is_some() + { println!(" AKp: message_id passes filter"); } } @@ -267,7 +275,10 @@ fn main() { // 2) Parallel validation (GPU-ready: par_all over collections) let ok_pmt = validate_payment_infos(&pain); let ok_tx = validate_credit_transfers(&pain); - println!("2) Validation (par_all): payment_infos valid = {}, credit_transfers valid = {}", ok_pmt, ok_tx); + println!( + "2) Validation (par_all): payment_infos valid = {}, credit_transfers valid = {}", + ok_pmt, ok_tx + ); // 3) par_map / par_flat_map: collect all amounts for downstream or GPU buffer let all_tx = all_credit_transfers(&pain); @@ -283,7 +294,10 @@ fn main() { // 5b) GPU-ready validation: flat amount buffer + par_all (can run on GPU via scale_par pattern) let amounts_ok = validate_all_amounts_positive(&pain); - println!("5b) GPU-ready validation (par_flat_map + par_all): all amounts > 0 = {}", amounts_ok); + println!( + "5b) GPU-ready validation (par_flat_map + par_all): all amounts > 0 = {}", + amounts_ok + ); // 6) PKp pipeline (filter + map) println!("6) PKp (PartialKeyPath):"); diff --git a/examples/performance_monitor_example.rs b/examples/performance_monitor_example.rs index dc9a3fe..1de2a77 100644 --- a/examples/performance_monitor_example.rs +++ b/examples/performance_monitor_example.rs @@ -18,7 +18,10 @@ fn main() { let avg = monitor.average_task_time(); println!("After 100 tasks of 50μs each:"); println!(" average_task_time() = {:?}", avg); - println!(" throughput_per_second(1) = {:.0}", monitor.throughput_per_second(1)); + println!( + " throughput_per_second(1) = {:.0}", + monitor.throughput_per_second(1) + ); // Add more tasks for _ in 0..900 { @@ -26,6 +29,9 @@ fn main() { } println!("\nAfter 1000 total tasks:"); println!(" average_task_time() = {:?}", monitor.average_task_time()); - println!(" throughput_per_second(1) = {:.0}", monitor.throughput_per_second(1)); + println!( + " throughput_per_second(1) = {:.0}", + monitor.throughput_per_second(1) + ); println!("\nDone."); } diff --git a/examples/pin_project_fair_race.rs b/examples/pin_project_fair_race.rs index 9c70200..4a5b28c 100644 --- a/examples/pin_project_fair_race.rs +++ b/examples/pin_project_fair_race.rs @@ -17,7 +17,7 @@ use std::task::{Context, Poll}; use key_paths_derive::Kp; use pin_project::pin_project; -use tokio::time::{sleep, Duration}; +use tokio::time::{Duration, sleep}; type BoxFuture = Pin + Send>>; @@ -111,10 +111,7 @@ async fn main() { assert_eq!(result2, "A completed"); // Keypath introspection before the race - let race3 = FairRaceFuture::new( - labeled_sleep(10, "left"), - labeled_sleep(20, "right"), - ); + let race3 = FairRaceFuture::new(labeled_sleep(10, "left"), labeled_sleep(20, "right")); println!("\n Before race, fair flag: {:?}", fair_kp.get(&race3)); let result3 = race3.await; println!(" Third race result: {:?}", result3); diff --git a/examples/pkp_akp_filter_typeid.rs b/examples/pkp_akp_filter_typeid.rs index 9caf0cb..47e79b0 100644 --- a/examples/pkp_akp_filter_typeid.rs +++ b/examples/pkp_akp_filter_typeid.rs @@ -47,7 +47,10 @@ fn main() { println!("All keypaths (count = {}):", person_kps.len()); for pkp in &person_kps { - println!(" root: Person, value: {}", type_id_name(pkp.value_type_id())); + println!( + " root: Person, value: {}", + type_id_name(pkp.value_type_id()) + ); } println!("\nFilter: value_type_id == String"); @@ -56,7 +59,10 @@ fn main() { .filter(|pkp| pkp.value_type_id() == TypeId::of::()) .collect(); for pkp in &string_kps { - println!(" root: Person, value: {}", type_id_name(pkp.value_type_id())); + println!( + " root: Person, value: {}", + type_id_name(pkp.value_type_id()) + ); } println!("\nFilter: value_type_id == i32"); @@ -65,7 +71,10 @@ fn main() { .filter(|pkp| pkp.value_type_id() == TypeId::of::()) .collect(); for pkp in &i32_kps { - println!(" root: Person, value: {}", type_id_name(pkp.value_type_id())); + println!( + " root: Person, value: {}", + type_id_name(pkp.value_type_id()) + ); } // ---- AKp (AnyKeyPath): Both Root and Value are type-erased ---- diff --git a/examples/pkp_akp_read_write_convert.rs b/examples/pkp_akp_read_write_convert.rs index 5f8a997..5b5ac1d 100644 --- a/examples/pkp_akp_read_write_convert.rs +++ b/examples/pkp_akp_read_write_convert.rs @@ -75,7 +75,7 @@ fn main() { // ---- 1. READ using PKp ---- println!("--- 1. Read using PKp::get_as ---"); let mut person = Person { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -165,7 +165,9 @@ fn main() { // After filtering AKp by root+value, we "convert" by using the known typed Kp let title_kp = Product::title(); - title_kp.get_mut(&mut product).map(|t| *t = "Super Gadget".to_string()); + title_kp + .get_mut(&mut product) + .map(|t| *t = "Super Gadget".to_string()); println!(" Product title after write: {}", product.title); let price_kp = Product::price(); diff --git a/examples/rayon_config_example.rs b/examples/rayon_config_example.rs index f8e5297..2391e8e 100644 --- a/examples/rayon_config_example.rs +++ b/examples/rayon_config_example.rs @@ -19,14 +19,20 @@ fn main() { let _pool_io = RayonConfig::io_bound().build().expect("io_bound"); println!(" - io_bound() -> {} threads", n * 2); - let _pool_mem = RayonConfig::memory_intensive().build().expect("memory_intensive"); + let _pool_mem = RayonConfig::memory_intensive() + .build() + .expect("memory_intensive"); println!(" - memory_intensive() -> {} threads", (n / 2).max(1)); - let _pool_lat = RayonConfig::latency_sensitive().build().expect("latency_sensitive"); + let _pool_lat = RayonConfig::latency_sensitive() + .build() + .expect("latency_sensitive"); println!(" - latency_sensitive() -> {} threads", (n / 2).max(2)); let phys = num_cpus::get_physical(); - let _pool_phys = RayonConfig::physical_cores_only().build().expect("physical_cores_only"); + let _pool_phys = RayonConfig::physical_cores_only() + .build() + .expect("physical_cores_only"); println!(" - physical_cores_only() -> {} threads\n", phys); // 2. OptimizationGuide presets (run work on data_pipeline only to avoid stack size variance) @@ -35,7 +41,11 @@ fn main() { let pool = OptimizationGuide::data_pipeline(); let start = Instant::now(); let sum: u32 = pool.install(|| data.par_iter().copied().sum()); - println!(" data_pipeline(): sum = {}, elapsed = {:?}", sum, start.elapsed()); + println!( + " data_pipeline(): sum = {}, elapsed = {:?}", + sum, + start.elapsed() + ); println!(" (Other presets: web_server, scientific_computing, real_time, machine_learning)"); println!("\nDone."); diff --git a/examples/rayon_env_example.rs b/examples/rayon_env_example.rs index dc96b05..3ccfc6c 100644 --- a/examples/rayon_env_example.rs +++ b/examples/rayon_env_example.rs @@ -12,13 +12,19 @@ fn main() { // Show current env (if set) let threads = env::var("RAYON_NUM_THREADS").unwrap_or_else(|_| "".into()); let stack = env::var("RAYON_STACK_SIZE").unwrap_or_else(|_| "".into()); - println!("Before: RAYON_NUM_THREADS = {}, RAYON_STACK_SIZE = {}", threads, stack); + println!( + "Before: RAYON_NUM_THREADS = {}, RAYON_STACK_SIZE = {}", + threads, stack + ); // Set defaults for this process RayonEnvConfig::configure_env(); let threads = env::var("RAYON_NUM_THREADS").unwrap(); let stack = env::var("RAYON_STACK_SIZE").unwrap(); - println!("After configure_env(): RAYON_NUM_THREADS = {}, RAYON_STACK_SIZE = {}\n", threads, stack); + println!( + "After configure_env(): RAYON_NUM_THREADS = {}, RAYON_STACK_SIZE = {}\n", + threads, stack + ); // Save to file let path = "rayon_example.conf"; @@ -33,7 +39,10 @@ fn main() { env::remove_var("RAYON_STACK_SIZE"); } RayonEnvConfig::load_from_file(path).expect("load config"); - println!("After load_from_file: RAYON_NUM_THREADS = {:?}", env::var("RAYON_NUM_THREADS")); + println!( + "After load_from_file: RAYON_NUM_THREADS = {:?}", + env::var("RAYON_NUM_THREADS") + ); fs::remove_file(path).ok(); println!("\nDone."); } diff --git a/examples/rayon_patterns_example.rs b/examples/rayon_patterns_example.rs index 5095b71..77049bb 100644 --- a/examples/rayon_patterns_example.rs +++ b/examples/rayon_patterns_example.rs @@ -14,25 +14,37 @@ fn main() { RayonPatterns::small_collection_optimization(&small, 500, |_| { count.fetch_add(1, Ordering::Relaxed); }); - println!("small_collection_optimization(small, min_len=500): count = {}", count.load(Ordering::Relaxed)); + println!( + "small_collection_optimization(small, min_len=500): count = {}", + count.load(Ordering::Relaxed) + ); let large: Vec = (0..5_000).collect(); count.store(0, Ordering::Relaxed); RayonPatterns::small_collection_optimization(&large, 500, |_| { count.fetch_add(1, Ordering::Relaxed); }); - println!("small_collection_optimization(large, min_len=500): count = {}\n", count.load(Ordering::Relaxed)); + println!( + "small_collection_optimization(large, min_len=500): count = {}\n", + count.load(Ordering::Relaxed) + ); // 2. efficient_sum (fold + reduce, no intermediate collect) let data: Vec = (0..10_000).collect(); let sum = RayonPatterns::efficient_sum(&data); let expected: u32 = (0..10_000).sum(); - println!("efficient_sum(0..10000) = {} (expected {})\n", sum, expected); + println!( + "efficient_sum(0..10000) = {} (expected {})\n", + sum, expected + ); // 3. reduce_lock_contention: per-chunk results, no shared mutex let items: Vec = (0..1_000).collect(); let strings = RayonPatterns::reduce_lock_contention(&items, 100, |&x| format!("{}", x * 2)); - println!("reduce_lock_contention(chunk_size=100): {} results", strings.len()); + println!( + "reduce_lock_contention(chunk_size=100): {} results", + strings.len() + ); println!(" first few: {:?}", &strings[..3.min(strings.len())]); println!("\nDone."); } diff --git a/examples/rayon_profiler_example.rs b/examples/rayon_profiler_example.rs index 76aa71a..426217b 100644 --- a/examples/rayon_profiler_example.rs +++ b/examples/rayon_profiler_example.rs @@ -21,7 +21,10 @@ fn main() { }, iterations, ); - println!("compare_parallel_vs_sequential ({} iterations):", iterations); + println!( + "compare_parallel_vs_sequential ({} iterations):", + iterations + ); println!(" sequential avg = {:?}", seq); println!(" parallel avg = {:?}", par); println!(" speedup = {:.2}x\n", speedup); diff --git a/examples/scale_par_gpu_validation.rs b/examples/scale_par_gpu_validation.rs index c4d8e54..9c51600 100644 --- a/examples/scale_par_gpu_validation.rs +++ b/examples/scale_par_gpu_validation.rs @@ -4,10 +4,10 @@ //! for GPU transfer, adaptive dispatch config, and parallel buffer scaling — all via keypaths. use key_paths_iter::scale_par::{ - adaptive_gpu_dispatch, count_nodes_by_kind, extract_gpu_data, par_flat_map_buffer_data, - par_scale_buffers, par_validate_buffers_non_empty, preprocess_sort_pairs, process_gpu_results, - slice_collection, validate_for_gpu, GpuBuffer, GpuComputePipeline, InteractionNet, NetNode, - NodeKind, + GpuBuffer, GpuComputePipeline, InteractionNet, NetNode, NodeKind, adaptive_gpu_dispatch, + count_nodes_by_kind, extract_gpu_data, par_flat_map_buffer_data, par_scale_buffers, + par_validate_buffers_non_empty, preprocess_sort_pairs, process_gpu_results, slice_collection, + validate_for_gpu, }; use rust_key_paths::Kp; use rust_key_paths::KpType; @@ -51,10 +51,15 @@ fn main() { } else { NodeKind::Ref }; - NetNode::new(kind, [i as u32 % 100, (i + 1) as u32 % 100, (i + 2) as u32 % 100]) + NetNode::new( + kind, + [i as u32 % 100, (i + 1) as u32 % 100, (i + 2) as u32 % 100], + ) }) .collect(), - active_pairs: (0..2000).map(|i| (i as u32 % 500, (i + 1) as u32 % 500)).collect(), + active_pairs: (0..2000) + .map(|i| (i as u32 % 500, (i + 1) as u32 % 500)) + .collect(), }, }; @@ -88,7 +93,10 @@ fn main() { // 5) Slice for batching (e.g. stream to GPU in chunks) let batch = slice_collection(&pairs_kp, &pipeline, 0, 100); - println!("First batch of pairs (slice 0..100): {} pairs.", batch.len()); + println!( + "First batch of pairs (slice 0..100): {} pairs.", + batch.len() + ); // 6) Keypath to buffers: we need a keypath to pipeline.cpu_state.buffers let buffers_kp: KpType<'static, GpuComputePipeline, Vec> = Kp::new( diff --git a/key-paths-derive/Cargo.toml b/key-paths-derive/Cargo.toml index 20930ea..83fac04 100644 --- a/key-paths-derive/Cargo.toml +++ b/key-paths-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "key-paths-derive" -version = "2.0.7" +version = "2.4.0" edition = "2024" description = "Proc-macro derive to generate keypath methods" license = "MPL-2.0" @@ -23,7 +23,7 @@ syn = { version = "2", features = ["full"] } [dev-dependencies] async-trait = "0.1" -rust-key-paths = { path = "../", version = "2.0.7", features = ["tokio", "parking_lot", "pin_project"] } +rust-key-paths = { path = "../", version = "2.3.0", features = ["tokio", "parking_lot", "pin_project"] } tokio = { version = "1.38", features = ["sync", "rt", "macros"] } parking_lot = "0.12" pin-project = "1.1" \ No newline at end of file diff --git a/key-paths-derive/src/lib.rs b/key-paths-derive/src/lib.rs index 4b48b2f..2776624 100644 --- a/key-paths-derive/src/lib.rs +++ b/key-paths-derive/src/lib.rs @@ -68,11 +68,15 @@ enum WrapperKind { // Arc with synchronization primitives (default) StdArcMutex, StdArcRwLock, + StdArcMutexOption, + StdArcRwLockOption, OptionStdArcMutex, OptionStdArcRwLock, // Synchronization primitives default StdMutex, StdRwLock, + StdMutexOption, + StdRwLockOption, OptionStdMutex, OptionStdRwLock, // Synchronization primitives (parking_lot) @@ -86,8 +90,12 @@ enum WrapperKind { // parking_lot ArcMutex, ArcRwLock, + ArcMutexOption, + ArcRwLockOption, OptionArcMutex, OptionArcRwLock, + MutexOption, + RwLockOption, // Arc with synchronization primitives (tokio::sync - requires tokio feature) TokioArcMutex, TokioArcRwLock, @@ -116,36 +124,53 @@ enum WrapperKind { PinnedBoxFuture, } -/// Helper function to check if a type path includes std::sync module +/// Helper function to check if a type path is exactly under std::sync (not lock_api or parking_lot). +/// Requires the path to start with "std" then "sync" so we don't confuse with RwLock<RawRwLock, T> (lock_api). fn is_std_sync_type(path: &syn::Path) -> bool { - // Check for paths like std::sync::Mutex, std::sync::RwLock let segments: Vec<_> = path.segments.iter().map(|s| s.ident.to_string()).collect(); segments.len() >= 2 - && segments.contains(&"std".to_string()) - && segments.contains(&"sync".to_string()) + && segments.get(0).map(|s| s.as_str()) == Some("std") + && segments.get(1).map(|s| s.as_str()) == Some("sync") } -/// Helper function to check if a type path includes tokio::sync module +/// Helper function to check if a type path is exactly under tokio::sync. fn is_tokio_sync_type(path: &syn::Path) -> bool { - // Check for paths like tokio::sync::Mutex, tokio::sync::RwLock let segments: Vec<_> = path.segments.iter().map(|s| s.ident.to_string()).collect(); segments.len() >= 2 - && segments.contains(&"tokio".to_string()) - && segments.contains(&"sync".to_string()) + && segments.get(0).map(|s| s.as_str()) == Some("tokio") + && segments.get(1).map(|s| s.as_str()) == Some("sync") } -/// Helper function to check if a type path includes std::sync::atomic module +/// Helper function to check if a type path is under parking_lot (RwLock/Mutex from lock_api). +/// Use so we never treat parking_lot::RwLock as std::sync::RwLock. +fn is_parking_lot_type(path: &syn::Path) -> bool { + path.segments.first().map(|s| s.ident == "parking_lot") == Some(true) +} + +/// Helper function to check if a type path is under std::sync::atomic (strict prefix). fn is_std_sync_atomic_type(path: &syn::Path) -> bool { let segments: Vec<_> = path.segments.iter().map(|s| s.ident.to_string()).collect(); - segments.contains(&"std".to_string()) - && segments.contains(&"sync".to_string()) - && segments.contains(&"atomic".to_string()) + segments.len() >= 3 + && segments.get(0).map(|s| s.as_str()) == Some("std") + && segments.get(1).map(|s| s.as_str()) == Some("sync") + && segments.get(2).map(|s| s.as_str()) == Some("atomic") } /// Atomic type idents (no type params): AtomicBool, AtomicI8, etc. const ATOMIC_TYPE_IDENTS: &[&str] = &[ - "AtomicBool", "AtomicI8", "AtomicI16", "AtomicI32", "AtomicI64", "AtomicI128", "AtomicIsize", - "AtomicU8", "AtomicU16", "AtomicU32", "AtomicU64", "AtomicU128", "AtomicUsize", + "AtomicBool", + "AtomicI8", + "AtomicI16", + "AtomicI32", + "AtomicI64", + "AtomicI128", + "AtomicIsize", + "AtomicU8", + "AtomicU16", + "AtomicU32", + "AtomicU64", + "AtomicU128", + "AtomicUsize", ]; fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { @@ -348,6 +373,21 @@ fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { return (WrapperKind::HashMapOption, inner_inner); } // BTreeMapOption is handled in the map block (HashMap/BTreeMap) + // Mutex> / RwLock> (yields LockKp value T) + // std::sync::Mutex> / RwLock> + ("Mutex", WrapperKind::Option) if is_std_sync_type(&tp.path) => { + return (WrapperKind::StdMutexOption, inner_inner); + } + ("RwLock", WrapperKind::Option) if is_std_sync_type(&tp.path) => { + return (WrapperKind::StdRwLockOption, inner_inner); + } + // parking_lot::Mutex> / RwLock>, and bare Mutex/RwLock assumed parking_lot + ("Mutex", WrapperKind::Option) => { + return (WrapperKind::MutexOption, inner_inner); + } + ("RwLock", WrapperKind::Option) => { + return (WrapperKind::RwLockOption, inner_inner); + } // std::sync variants (when inner is StdMutex/StdRwLock) ("Arc", WrapperKind::StdMutex) => { return (WrapperKind::StdArcMutex, inner_inner); @@ -355,6 +395,12 @@ fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { ("Arc", WrapperKind::StdRwLock) => { return (WrapperKind::StdArcRwLock, inner_inner); } + ("Arc", WrapperKind::StdMutexOption) => { + return (WrapperKind::StdArcMutexOption, inner_inner); + } + ("Arc", WrapperKind::StdRwLockOption) => { + return (WrapperKind::StdArcRwLockOption, inner_inner); + } // parking_lot variants (default - when inner is Mutex/RwLock without std::sync prefix) ("Arc", WrapperKind::Mutex) => { return (WrapperKind::ArcMutex, inner_inner); @@ -362,6 +408,12 @@ fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { ("Arc", WrapperKind::RwLock) => { return (WrapperKind::ArcRwLock, inner_inner); } + ("Arc", WrapperKind::MutexOption) => { + return (WrapperKind::ArcMutexOption, inner_inner); + } + ("Arc", WrapperKind::RwLockOption) => { + return (WrapperKind::ArcRwLockOption, inner_inner); + } // tokio::sync variants (when inner is TokioMutex/TokioRwLock) ("Arc", WrapperKind::TokioMutex) => { return (WrapperKind::TokioArcMutex, inner_inner); @@ -386,34 +438,49 @@ fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { "LinkedList" => (WrapperKind::LinkedList, Some(inner.clone())), "BinaryHeap" => (WrapperKind::BinaryHeap, Some(inner.clone())), "Result" => (WrapperKind::Result, Some(inner.clone())), - // For std::sync::Mutex and std::sync::RwLock, use Std variants + // Explicit parking_lot path (lock_api::RwLock) — never treat as std + "Mutex" if is_parking_lot_type(&tp.path) => { + (WrapperKind::Mutex, Some(inner.clone())) + } + "RwLock" if is_parking_lot_type(&tp.path) => { + (WrapperKind::RwLock, Some(inner.clone())) + } + // std::sync::Mutex and std::sync::RwLock only when path starts with std::sync "Mutex" if is_std_sync => { (WrapperKind::StdMutex, Some(inner.clone())) } "RwLock" if is_std_sync => { (WrapperKind::StdRwLock, Some(inner.clone())) } - // For tokio::sync::Mutex and tokio::sync::RwLock, use Tokio variants + // tokio::sync::Mutex and tokio::sync::RwLock "Mutex" if is_tokio_sync => { (WrapperKind::TokioMutex, Some(inner.clone())) } "RwLock" if is_tokio_sync => { (WrapperKind::TokioRwLock, Some(inner.clone())) } - // Default: parking_lot (no std::sync or tokio::sync prefix) + // Default: parking_lot (bare Mutex/RwLock or unknown path) "Mutex" => (WrapperKind::Mutex, Some(inner.clone())), "RwLock" => (WrapperKind::RwLock, Some(inner.clone())), "Weak" => (WrapperKind::Weak, Some(inner.clone())), "Tagged" => (WrapperKind::Tagged, Some(inner.clone())), "Cow" => (WrapperKind::Cow, Some(inner.clone())), - "AtomicPtr" if is_std_sync_atomic_type(&tp.path) => (WrapperKind::Atomic, None), + "AtomicPtr" if is_std_sync_atomic_type(&tp.path) => { + (WrapperKind::Atomic, None) + } "Pin" => (WrapperKind::Pin, Some(inner.clone())), "Cell" => (WrapperKind::Cell, Some(inner.clone())), "RefCell" => (WrapperKind::RefCell, Some(inner.clone())), - "OnceCell" | "OnceLock" => (WrapperKind::OnceCell, Some(inner.clone())), + "OnceCell" | "OnceLock" => { + (WrapperKind::OnceCell, Some(inner.clone())) + } "Lazy" | "LazyLock" => (WrapperKind::Lazy, Some(inner.clone())), - "PhantomData" => (WrapperKind::PhantomData, Some(inner.clone())), - "Range" | "RangeInclusive" => (WrapperKind::Range, Some(inner.clone())), + "PhantomData" => { + (WrapperKind::PhantomData, Some(inner.clone())) + } + "Range" | "RangeInclusive" => { + (WrapperKind::Range, Some(inner.clone())) + } _ => (WrapperKind::None, None), }; } @@ -439,9 +506,10 @@ fn extract_wrapper_inner_type(ty: &Type) -> (WrapperKind, Option) { /// Check if a field has the #[pin] attribute (pin_project pattern). fn field_has_pin_attr(field: &syn::Field) -> bool { - field.attrs.iter().any(|attr| { - attr.path().get_ident().map(|i| i == "pin").unwrap_or(false) - }) + field + .attrs + .iter() + .any(|attr| attr.path().get_ident().map(|i| i == "pin").unwrap_or(false)) } /// Check if a type is a Future (dyn Future, impl Future, or Box). @@ -451,7 +519,9 @@ fn is_future_type(ty: &Type) -> bool { match ty { Type::TraitObject(trait_obj) => trait_obj.bounds.iter().any(|b| { if let TypeParamBound::Trait(t) = b { - t.path.segments.last() + t.path + .segments + .last() .map(|s| s.ident == "Future") .unwrap_or(false) } else { @@ -460,7 +530,9 @@ fn is_future_type(ty: &Type) -> bool { }), Type::ImplTrait(impl_trait) => impl_trait.bounds.iter().any(|b| { if let TypeParamBound::Trait(t) = b { - t.path.segments.last() + t.path + .segments + .last() .map(|s| s.ident == "Future") .unwrap_or(false) } else { @@ -568,12 +640,12 @@ fn to_snake_case(name: &str) -> String { } /// Derive macro for generating simple keypath methods. -/// +/// /// Generates one method per field: `StructName::field_name()` that returns a `Kp`. /// Intelligently handles wrapper types (Option, Vec, Box, Arc, etc.) to generate appropriate keypaths. -/// +/// /// # Example -/// +/// /// ```ignore /// #[derive(Kp)] /// struct Person { @@ -582,7 +654,7 @@ fn to_snake_case(name: &str) -> String { /// email: Option, /// addresses: Vec, /// } -/// +/// /// // Generates: /// // impl Person { /// // pub fn name() -> Kp<...> { ... } @@ -606,7 +678,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { tokens.extend(quote! { /// Returns a generic identity keypath for this type #[inline(always)] - pub fn identity_typed() -> rust_key_paths::Kp< + pub fn _identity() -> rust_key_paths::Kp< #name, #name, Root, @@ -626,16 +698,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { ) } - /// Returns a simple identity keypath for this type - #[inline(always)] - pub fn identity() -> rust_key_paths::KpType<'static, #name, #name> { - rust_key_paths::Kp::new( - |r: &#name| Some(r), - |r: &mut #name| Some(r) - ) - } + // /// Returns a simple identity keypath for this type + // #[inline(always)] + // pub fn identity() -> rust_key_paths::KpType<'static, #name, #name> { + // rust_key_paths::Kp::new( + // |r: &#name| Some(r), + // |r: &mut #name| Some(r) + // ) + // } }); - + // When struct has #[pin] fields, generated code calls this.project() which must // be provided by #[pin_project]. If missing, user gets: no method named `project`. @@ -679,6 +751,44 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionBox, Some(inner_ty)) => { + // For Option>, keypath to T: get returns Option<&T> via as_deref() + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#field_ident.as_deref(), + |root: &mut #name| root.#field_ident.as_deref_mut(), + ) + } + }); + } + (WrapperKind::OptionRc, Some(inner_ty)) => { + // For Option>, keypath to T: get returns Option<&T> via as_deref() + // Setter: as_mut() gives Option<&mut Rc>, and_then(Rc::get_mut) gives Option<&mut T> + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#field_ident.as_deref(), + |root: &mut #name| root.#field_ident.as_mut().and_then(std::rc::Rc::get_mut), + ) + } + }); + } + (WrapperKind::OptionArc, Some(inner_ty)) => { + // For Option>, keypath to T: get returns Option<&T> via as_deref() + // Setter: as_mut() gives Option<&mut Arc>, and_then(Arc::get_mut) gives Option<&mut T> + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#field_ident.as_deref(), + |root: &mut #name| root.#field_ident.as_mut().and_then(std::sync::Arc::get_mut), + ) + } + }); + } (WrapperKind::OptionVecDeque, Some(_inner_ty)) | (WrapperKind::OptionLinkedList, Some(_inner_ty)) | (WrapperKind::OptionBinaryHeap, Some(_inner_ty)) @@ -749,7 +859,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } } - (WrapperKind::BTreeMap, Some(inner_ty)) | (WrapperKind::BTreeMapOption, Some(inner_ty)) => { + (WrapperKind::BTreeMap, Some(inner_ty)) + | (WrapperKind::BTreeMapOption, Some(inner_ty)) => { if let Some((key_ty, _)) = extract_map_key_value(ty) { tokens.extend(quote! { #[inline(always)] @@ -795,6 +906,42 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::BoxOption, Some(inner_ty)) => { + // For Box>, keypath to T: deref Box to Option, then Option::as_ref/as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#field_ident).as_ref(), + |root: &mut #name| (&mut *root.#field_ident).as_mut(), + ) + } + }); + } + (WrapperKind::RcOption, Some(inner_ty)) => { + // For Rc>, keypath to T: deref Rc to &Option, then Option::as_ref; set = Rc::get_mut then as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#field_ident).as_ref(), + |root: &mut #name| std::rc::Rc::get_mut(&mut root.#field_ident).and_then(std::option::Option::as_mut), + ) + } + }); + } + (WrapperKind::ArcOption, Some(inner_ty)) => { + // For Arc>, keypath to T: deref Arc to &Option, then Option::as_ref; set = Arc::get_mut then as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#field_ident).as_ref(), + |root: &mut #name| std::sync::Arc::get_mut(&mut root.#field_ident).and_then(std::option::Option::as_mut), + ) + } + }); + } (WrapperKind::Pin, Some(inner_ty)) => { let kp_inner_fn = format_ident!("{}_inner", field_ident); tokens.extend(quote! { @@ -951,7 +1098,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - + (WrapperKind::OptionCow, Some(inner_ty)) => { // For Option> tokens.extend(quote! { @@ -988,7 +1135,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::HashSet, Some(inner_ty)) | (WrapperKind::HashSetOption, Some(inner_ty)) => { + (WrapperKind::HashSet, Some(inner_ty)) + | (WrapperKind::HashSetOption, Some(inner_ty)) => { let kp_at_fn = format_ident!("{}_at", field_ident); tokens.extend(quote! { @@ -1014,7 +1162,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::BTreeSet, Some(inner_ty)) | (WrapperKind::BTreeSetOption, Some(inner_ty)) => { + (WrapperKind::BTreeSet, Some(inner_ty)) + | (WrapperKind::BTreeSetOption, Some(inner_ty)) => { let kp_at_fn = format_ident!("{}_at", field_ident); tokens.extend(quote! { @@ -1040,7 +1189,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::VecDeque, Some(inner_ty)) | (WrapperKind::VecDequeOption, Some(inner_ty)) => { + (WrapperKind::VecDeque, Some(inner_ty)) + | (WrapperKind::VecDequeOption, Some(inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -1058,7 +1208,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::LinkedList, Some(_inner_ty)) | (WrapperKind::LinkedListOption, Some(_inner_ty)) => { + (WrapperKind::LinkedList, Some(_inner_ty)) + | (WrapperKind::LinkedListOption, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -1069,7 +1220,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::BinaryHeap, Some(_inner_ty)) | (WrapperKind::BinaryHeapOption, Some(_inner_ty)) => { + (WrapperKind::BinaryHeap, Some(_inner_ty)) + | (WrapperKind::BinaryHeapOption, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -1094,16 +1246,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } (WrapperKind::StdArcMutex, Some(inner_ty)) => { // For Arc> - let kp_lock_fn = format_ident!("{}_lock", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpArcMutexFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcMutexFor<#name, #ty, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1120,16 +1272,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } (WrapperKind::StdArcRwLock, Some(inner_ty)) => { // For Arc> - let kp_lock_fn = format_ident!("{}_lock", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpArcRwLockFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcRwLockFor<#name, #ty, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1144,18 +1296,70 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::StdArcMutexOption, Some(inner_ty)) => { + // For Arc>> — LockKp value T (extract from Option); guard gives &Option + let kp_lock_fn = format_ident!("{}_kp", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcMutexOptionFor<#name, #ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ), + rust_key_paths::lock::ArcMutexAccess::>::new(), + rust_key_paths::Kp::new( + Option::<#inner_ty>::as_ref, + Option::<#inner_ty>::as_mut, + ), + ) + } + }); + } + (WrapperKind::StdArcRwLockOption, Some(inner_ty)) => { + // For Arc>> — LockKp value T (extract from Option); guard gives &Option + let kp_lock_fn = format_ident!("{}_kp", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcRwLockOptionFor<#name, #ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ), + rust_key_paths::lock::ArcRwLockAccess::>::new(), + rust_key_paths::Kp::new( + Option::<#inner_ty>::as_ref, + Option::<#inner_ty>::as_mut, + ), + ) + } + }); + } (WrapperKind::ArcRwLock, Some(inner_ty)) => { // For Arc> (requires rust-key-paths "parking_lot" feature) - let kp_lock_fn = format_ident!("{}_lock", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpParkingLotRwLockFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotRwLockFor<#name, #ty, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1172,16 +1376,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } (WrapperKind::ArcMutex, Some(inner_ty)) => { // For Arc> (requires rust-key-paths "parking_lot" feature) - let kp_lock_fn = format_ident!("{}_lock", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpParkingLotMutexFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotMutexFor<#name, #ty, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1196,6 +1400,58 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::ArcMutexOption, Some(inner_ty)) => { + // For Arc>> — LockKp value T (extract from Option); guard gives &Option + let kp_lock_fn = format_ident!("{}_kp", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotMutexOptionFor<#name, #ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ), + rust_key_paths::lock::ParkingLotMutexAccess::>::new(), + rust_key_paths::Kp::new( + Option::<#inner_ty>::as_ref, + Option::<#inner_ty>::as_mut, + ), + ) + } + }); + } + (WrapperKind::ArcRwLockOption, Some(inner_ty)) => { + // For Arc>> — LockKp value T (extract from Option); guard gives &Option + let kp_lock_fn = format_ident!("{}_kp", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotRwLockOptionFor<#name, #ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ), + rust_key_paths::lock::ParkingLotRwLockAccess::>::new(), + rust_key_paths::Kp::new( + Option::<#inner_ty>::as_ref, + Option::<#inner_ty>::as_mut, + ), + ) + } + }); + } (WrapperKind::Mutex, Some(_inner_ty)) | (WrapperKind::StdMutex, Some(_inner_ty)) => { // For Mutex, return keypath to container @@ -1223,16 +1479,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::TokioArcMutex, Some(inner_ty)) => { - let kp_async_fn = format_ident!("{}_async", field_ident); + let kp_async_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1248,16 +1504,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::TokioArcRwLock, Some(inner_ty)) => { - let kp_async_fn = format_ident!("{}_async", field_ident); + let kp_async_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), @@ -1273,16 +1529,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcMutex, Some(inner_ty)) => { - let kp_async_fn = format_ident!("{}_async", field_ident); + let kp_async_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1298,16 +1554,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcRwLock, Some(inner_ty)) => { - let kp_async_fn = format_ident!("{}_async", field_ident); + let kp_async_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1323,23 +1579,23 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionStdArcMutex, Some(inner_ty)) => { - let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); - let kp_lock_fn = format_ident!("{}_lock", field_ident); + // let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { - rust_key_paths::Kp::new( - |root: &#name| root.#field_ident.as_ref(), - |root: &mut #name| root.#field_ident.as_mut(), - ) - } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpArcMutexFor<#name, std::sync::Arc>, #inner_ty> { + // pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { + // rust_key_paths::Kp::new( + // |root: &#name| root.#field_ident.as_ref(), + // |root: &mut #name| root.#field_ident.as_mut(), + // ) + // } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcMutexFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1355,23 +1611,23 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionArcMutex, Some(inner_ty)) => { - let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); - let kp_lock_fn = format_ident!("{}_lock", field_ident); + // let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { - rust_key_paths::Kp::new( - |root: &#name| root.#field_ident.as_ref(), - |root: &mut #name| root.#field_ident.as_mut(), - ) - } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpParkingLotMutexFor<#name, std::sync::Arc>, #inner_ty> { + // pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { + // rust_key_paths::Kp::new( + // |root: &#name| root.#field_ident.as_ref(), + // |root: &mut #name| root.#field_ident.as_mut(), + // ) + // } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotMutexFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1387,23 +1643,23 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionStdArcRwLock, Some(inner_ty)) => { - let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); - let kp_lock_fn = format_ident!("{}_lock", field_ident); + // let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { - rust_key_paths::Kp::new( - |root: &#name| root.#field_ident.as_ref(), - |root: &mut #name| root.#field_ident.as_mut(), - ) - } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpArcRwLockFor<#name, std::sync::Arc>, #inner_ty> { + // pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { + // rust_key_paths::Kp::new( + // |root: &#name| root.#field_ident.as_ref(), + // |root: &mut #name| root.#field_ident.as_mut(), + // ) + // } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpArcRwLockFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1419,23 +1675,23 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionArcRwLock, Some(inner_ty)) => { - let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); - let kp_lock_fn = format_ident!("{}_lock", field_ident); + // let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + let kp_lock_fn = format_ident!("{}_kp", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_lock_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), ) } - pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { - rust_key_paths::Kp::new( - |root: &#name| root.#field_ident.as_ref(), - |root: &mut #name| root.#field_ident.as_mut(), - ) - } - pub fn #kp_lock_fn() -> rust_key_paths::lock::LockKpParkingLotRwLockFor<#name, std::sync::Arc>, #inner_ty> { + // pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, std::sync::Arc>> { + // rust_key_paths::Kp::new( + // |root: &#name| root.#field_ident.as_ref(), + // |root: &mut #name| root.#field_ident.as_mut(), + // ) + // } + pub fn #kp_fn() -> rust_key_paths::lock::LockKpParkingLotRwLockFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::lock::LockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#field_ident.as_ref(), @@ -1450,12 +1706,11 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionStdMutex, Some(inner_ty)) - | (WrapperKind::OptionMutex, Some(inner_ty)) => { + (WrapperKind::OptionStdMutex, Some(inner_ty)) => { let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), @@ -1469,12 +1724,29 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionStdRwLock, Some(inner_ty)) - | (WrapperKind::OptionRwLock, Some(inner_ty)) => { + (WrapperKind::OptionMutex, Some(inner_ty)) => { let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, parking_lot::Mutex<#inner_ty>> { + rust_key_paths::Kp::new( + |root: &#name| root.#field_ident.as_ref(), + |root: &mut #name| root.#field_ident.as_mut(), + ) + } + }); + } + (WrapperKind::OptionStdRwLock, Some(inner_ty)) => { + let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#field_ident), |root: &mut #name| Some(&mut root.#field_ident), @@ -1488,6 +1760,24 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionRwLock, Some(inner_ty)) => { + let kp_unlocked_fn = format_ident!("{}_unlocked", field_ident); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, parking_lot::RwLock<#inner_ty>> { + rust_key_paths::Kp::new( + |root: &#name| root.#field_ident.as_ref(), + |root: &mut #name| root.#field_ident.as_mut(), + ) + } + }); + } (WrapperKind::Weak, Some(_inner_ty)) => { // For Weak, return keypath to container tokens.extend(quote! { @@ -1624,13 +1914,14 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionRefCell, Some(_inner_ty)) => { + (WrapperKind::OptionRefCell, Some(inner_ty)) => { + // Option>: keypath to T via borrow()/borrow_mut(); get returns Option> so caller holds guard (deref for &V) tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_fn() -> rust_key_paths::KpOptionRefCellType<'_, #name, #inner_ty> { rust_key_paths::Kp::new( - |root: &#name| Some(&root.#field_ident), - |root: &mut #name| Some(&mut root.#field_ident), + |root: &#name| root.#field_ident.as_ref().map(|r| r.borrow()), + |root: &mut #name| root.#field_ident.as_ref().map(|r| r.borrow_mut()), ) } }); @@ -1717,7 +2008,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } } } - + tokens } Fields::Unnamed(unnamed) => { @@ -1778,6 +2069,39 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionBox, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_deref(), + |root: &mut #name| root.#idx_lit.as_deref_mut(), + ) + } + }); + } + (WrapperKind::OptionRc, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_deref(), + |root: &mut #name| root.#idx_lit.as_mut().and_then(std::rc::Rc::get_mut), + ) + } + }); + } + (WrapperKind::OptionArc, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_deref(), + |root: &mut #name| root.#idx_lit.as_mut().and_then(std::sync::Arc::get_mut), + ) + } + }); + } (WrapperKind::OptionVecDeque, Some(_inner_ty)) | (WrapperKind::OptionLinkedList, Some(_inner_ty)) | (WrapperKind::OptionBinaryHeap, Some(_inner_ty)) @@ -1847,7 +2171,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } } - (WrapperKind::BTreeMap, Some(inner_ty)) | (WrapperKind::BTreeMapOption, Some(inner_ty)) => { + (WrapperKind::BTreeMap, Some(inner_ty)) + | (WrapperKind::BTreeMapOption, Some(inner_ty)) => { if let Some((key_ty, _)) = extract_map_key_value(ty) { tokens.extend(quote! { #[inline(always)] @@ -1893,6 +2218,39 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::BoxOption, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#idx_lit).as_ref(), + |root: &mut #name| (&mut *root.#idx_lit).as_mut(), + ) + } + }); + } + (WrapperKind::RcOption, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#idx_lit).as_ref(), + |root: &mut #name| std::rc::Rc::get_mut(&mut root.#idx_lit).and_then(std::option::Option::as_mut), + ) + } + }); + } + (WrapperKind::ArcOption, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| (&*root.#idx_lit).as_ref(), + |root: &mut #name| std::sync::Arc::get_mut(&mut root.#idx_lit).and_then(std::option::Option::as_mut), + ) + } + }); + } (WrapperKind::Pin, Some(inner_ty)) => { let kp_inner_fn = format_ident!("{}_inner", kp_fn); tokens.extend(quote! { @@ -1957,7 +2315,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - + (WrapperKind::Cow, Some(inner_ty)) => { tokens.extend(quote! { #[inline(always)] @@ -1969,7 +2327,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - + (WrapperKind::OptionCow, Some(inner_ty)) => { tokens.extend(quote! { #[inline(always)] @@ -2003,7 +2361,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::HashSet, Some(inner_ty)) | (WrapperKind::HashSetOption, Some(inner_ty)) => { + (WrapperKind::HashSet, Some(inner_ty)) + | (WrapperKind::HashSetOption, Some(inner_ty)) => { let kp_at_fn = format_ident!("f{}_at", idx); tokens.extend(quote! { @@ -2029,7 +2388,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::BTreeSet, Some(inner_ty)) | (WrapperKind::BTreeSetOption, Some(inner_ty)) => { + (WrapperKind::BTreeSet, Some(inner_ty)) + | (WrapperKind::BTreeSetOption, Some(inner_ty)) => { let kp_at_fn = format_ident!("f{}_at", idx); tokens.extend(quote! { @@ -2055,7 +2415,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::VecDeque, Some(inner_ty)) | (WrapperKind::VecDequeOption, Some(inner_ty)) => { + (WrapperKind::VecDeque, Some(inner_ty)) + | (WrapperKind::VecDequeOption, Some(inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -2073,7 +2434,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::LinkedList, Some(_inner_ty)) | (WrapperKind::LinkedListOption, Some(_inner_ty)) => { + (WrapperKind::LinkedList, Some(_inner_ty)) + | (WrapperKind::LinkedListOption, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -2084,7 +2446,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::BinaryHeap, Some(_inner_ty)) | (WrapperKind::BinaryHeapOption, Some(_inner_ty)) => { + (WrapperKind::BinaryHeap, Some(_inner_ty)) + | (WrapperKind::BinaryHeapOption, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -2131,16 +2494,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::TokioArcMutex, Some(inner_ty)) => { - let kp_async_fn = format_ident!("f{}_async", idx); + let kp_async_fn = format_ident!("f{}_kp", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), @@ -2156,16 +2519,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::TokioArcRwLock, Some(inner_ty)) => { - let kp_async_fn = format_ident!("f{}_async", idx); + let kp_async_fn = format_ident!("f{}_kp", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #ty, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), @@ -2181,16 +2544,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcMutex, Some(inner_ty)) => { - let kp_async_fn = format_ident!("f{}_async", idx); + let kp_async_fn = format_ident!("f{}_kp", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#idx_lit.as_ref(), @@ -2206,16 +2569,16 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcRwLock, Some(inner_ty)) => { - let kp_async_fn = format_ident!("f{}_async", idx); + let kp_async_fn = format_ident!("f{}_kp", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_async_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), ) } - pub fn #kp_async_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #kp_fn() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| root.#idx_lit.as_ref(), @@ -2302,12 +2665,11 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionStdMutex, Some(inner_ty)) - | (WrapperKind::OptionMutex, Some(inner_ty)) => { + (WrapperKind::OptionStdMutex, Some(inner_ty)) => { let kp_unlocked_fn = format_ident!("f{}_unlocked", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), @@ -2321,12 +2683,29 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionStdRwLock, Some(inner_ty)) - | (WrapperKind::OptionRwLock, Some(inner_ty)) => { + (WrapperKind::OptionMutex, Some(inner_ty)) => { let kp_unlocked_fn = format_ident!("f{}_unlocked", idx); tokens.extend(quote! { #[inline(always)] - pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, parking_lot::Mutex<#inner_ty>> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_ref(), + |root: &mut #name| root.#idx_lit.as_mut(), + ) + } + }); + } + (WrapperKind::OptionStdRwLock, Some(inner_ty)) => { + let kp_unlocked_fn = format_ident!("f{}_unlocked", idx); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { rust_key_paths::Kp::new( |root: &#name| Some(&root.#idx_lit), |root: &mut #name| Some(&mut root.#idx_lit), @@ -2340,6 +2719,24 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionRwLock, Some(inner_ty)) => { + let kp_unlocked_fn = format_ident!("f{}_unlocked", idx); + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + pub fn #kp_unlocked_fn() -> rust_key_paths::KpType<'static, #name, parking_lot::RwLock<#inner_ty>> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_ref(), + |root: &mut #name| root.#idx_lit.as_mut(), + ) + } + }); + } (WrapperKind::Weak, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] @@ -2439,10 +2836,13 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::Cell, Some(_inner_ty)) | (WrapperKind::RefCell, Some(_inner_ty)) - | (WrapperKind::PhantomData, Some(_inner_ty)) | (WrapperKind::Range, Some(_inner_ty)) - | (WrapperKind::OptionCell, Some(_inner_ty)) | (WrapperKind::OptionRefCell, Some(_inner_ty)) - | (WrapperKind::OptionPhantomData, Some(_inner_ty)) | (WrapperKind::OptionRange, Some(_inner_ty)) => { + (WrapperKind::Cell, Some(_inner_ty)) + | (WrapperKind::RefCell, Some(_inner_ty)) + | (WrapperKind::PhantomData, Some(_inner_ty)) + | (WrapperKind::Range, Some(_inner_ty)) + | (WrapperKind::OptionCell, Some(_inner_ty)) + | (WrapperKind::OptionPhantomData, Some(_inner_ty)) + | (WrapperKind::OptionRange, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -2453,6 +2853,17 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionRefCell, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpOptionRefCellType<'_, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| root.#idx_lit.as_ref().map(|r| r.borrow()), + |root: &mut #name| root.#idx_lit.as_ref().map(|r| r.borrow_mut()), + ) + } + }); + } (WrapperKind::Reference, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] @@ -2493,8 +2904,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } Fields::Unit => { return syn::Error::new(input_span, "Kp derive does not support unit structs") - .to_compile_error() - .into(); + .to_compile_error() + .into(); } }, Data::Enum(data_enum) => { @@ -2851,11 +3262,99 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::TokioArcMutex, Some(inner_ty)) => { - let snake_async = format_ident!("{}_async", snake); + (WrapperKind::StdArcMutexOption, Some(inner_ty)) => { + let snake_lock = format_ident!("{}_lock", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ) + } + pub fn #snake_lock() -> rust_key_paths::lock::LockKpArcMutexOptionFor<#name, #field_ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ), + rust_key_paths::lock::ArcMutexAccess::>::new(), + rust_key_paths::Kp::new(Option::<#inner_ty>::as_ref, Option::<#inner_ty>::as_mut), + ) + } + }); + } + (WrapperKind::StdArcRwLockOption, Some(inner_ty)) => { + let snake_lock = format_ident!("{}_lock", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ) + } + pub fn #snake_lock() -> rust_key_paths::lock::LockKpArcRwLockOptionFor<#name, #field_ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ), + rust_key_paths::lock::ArcRwLockAccess::>::new(), + rust_key_paths::Kp::new(Option::<#inner_ty>::as_ref, Option::<#inner_ty>::as_mut), + ) + } + }); + } + (WrapperKind::ArcMutexOption, Some(inner_ty)) => { + let snake_lock = format_ident!("{}_lock", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ) + } + pub fn #snake_lock() -> rust_key_paths::lock::LockKpParkingLotMutexOptionFor<#name, #field_ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ), + rust_key_paths::lock::ParkingLotMutexAccess::>::new(), + rust_key_paths::Kp::new(Option::<#inner_ty>::as_ref, Option::<#inner_ty>::as_mut), + ) + } + }); + } + (WrapperKind::ArcRwLockOption, Some(inner_ty)) => { + let snake_lock = format_ident!("{}_lock", snake); tokens.extend(quote! { #[inline(always)] pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ) + } + pub fn #snake_lock() -> rust_key_paths::lock::LockKpParkingLotRwLockOptionFor<#name, #field_ty, #inner_ty> { + rust_key_paths::lock::LockKp::new( + rust_key_paths::Kp::new( + |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + |root: &mut #name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, + ), + rust_key_paths::lock::ParkingLotRwLockAccess::>::new(), + rust_key_paths::Kp::new(Option::<#inner_ty>::as_ref, Option::<#inner_ty>::as_mut), + ) + } + }); + } + (WrapperKind::TokioArcMutex, Some(inner_ty)) => { + let snake_async = format_ident!("{}_kp", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake_async() -> rust_key_paths::KpType<'static, #name, #field_ty> { rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), @@ -2867,7 +3366,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }, ) } - pub fn #snake_async() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #field_ty, #inner_ty> { + pub fn #snake() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, #field_ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, @@ -2880,10 +3379,10 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::TokioArcRwLock, Some(inner_ty)) => { - let snake_async = format_ident!("{}_async", snake); + let snake_async = format_ident!("{}_kp", snake); tokens.extend(quote! { #[inline(always)] - pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + pub fn #snake_async() -> rust_key_paths::KpType<'static, #name, #field_ty> { rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), @@ -2895,7 +3394,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }, ) } - pub fn #snake_async() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #field_ty, #inner_ty> { + pub fn #snake() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, #field_ty, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), _ => None }, @@ -2908,10 +3407,10 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcMutex, Some(inner_ty)) => { - let snake_async = format_ident!("{}_async", snake); + let snake_async = format_ident!("{}_kp", snake); tokens.extend(quote! { #[inline(always)] - pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + pub fn #snake_async() -> rust_key_paths::KpType<'static, #name, #field_ty> { rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), @@ -2923,7 +3422,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }, ) } - pub fn #snake_async() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #snake() -> rust_key_paths::async_lock::AsyncLockKpMutexFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => inner.as_ref(), _ => None }, @@ -2936,10 +3435,10 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }); } (WrapperKind::OptionTokioArcRwLock, Some(inner_ty)) => { - let snake_async = format_ident!("{}_async", snake); + let snake_async = format_ident!("{}_kp", snake); tokens.extend(quote! { #[inline(always)] - pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + pub fn #snake_async() -> rust_key_paths::KpType<'static, #name, #field_ty> { rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => Some(inner), @@ -2951,7 +3450,7 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { }, ) } - pub fn #snake_async() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { + pub fn #snake() -> rust_key_paths::async_lock::AsyncLockKpRwLockFor<#name, std::sync::Arc>, #inner_ty> { rust_key_paths::async_lock::AsyncLockKp::new( rust_key_paths::Kp::new( |root: &#name| match root { #name::#v_ident(inner) => inner.as_ref(), _ => None }, @@ -3213,6 +3712,112 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionBox, Some(inner_ty)) => { + // Option>: keypath to T via as_deref() / as_deref_mut() + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => inner.as_deref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_deref_mut(), + _ => None, + }, + ) + } + }); + } + (WrapperKind::BoxOption, Some(inner_ty)) => { + // Box>: keypath to T; inner is &Box>, deref then Option::as_ref/as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => (&*inner).as_ref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => (&mut *inner).as_mut(), + _ => None, + }, + ) + } + }); + } + (WrapperKind::RcOption, Some(inner_ty)) => { + // Rc>: keypath to T; get = (&*inner).as_ref(), set = Rc::get_mut then as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => (&*inner).as_ref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => std::rc::Rc::get_mut(inner).and_then(std::option::Option::as_mut), + _ => None, + }, + ) + } + }); + } + (WrapperKind::ArcOption, Some(inner_ty)) => { + // Arc>: keypath to T; get = (&*inner).as_ref(), set = Arc::get_mut then as_mut + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => (&*inner).as_ref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => std::sync::Arc::get_mut(inner).and_then(std::option::Option::as_mut), + _ => None, + }, + ) + } + }); + } + (WrapperKind::OptionRc, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => inner.as_deref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(std::rc::Rc::get_mut), + _ => None, + }, + ) + } + }); + } + (WrapperKind::OptionArc, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => inner.as_deref(), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(std::sync::Arc::get_mut), + _ => None, + }, + ) + } + }); + } (WrapperKind::OptionCow, Some(inner_ty)) => { tokens.extend(quote! { #[inline(always)] @@ -3351,10 +3956,13 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::Cell, Some(_inner_ty)) | (WrapperKind::RefCell, Some(_inner_ty)) - | (WrapperKind::PhantomData, Some(_inner_ty)) | (WrapperKind::Range, Some(_inner_ty)) - | (WrapperKind::OptionCell, Some(_inner_ty)) | (WrapperKind::OptionRefCell, Some(_inner_ty)) - | (WrapperKind::OptionPhantomData, Some(_inner_ty)) | (WrapperKind::OptionRange, Some(_inner_ty)) => { + (WrapperKind::Cell, Some(_inner_ty)) + | (WrapperKind::RefCell, Some(_inner_ty)) + | (WrapperKind::PhantomData, Some(_inner_ty)) + | (WrapperKind::Range, Some(_inner_ty)) + | (WrapperKind::OptionCell, Some(_inner_ty)) + | (WrapperKind::OptionPhantomData, Some(_inner_ty)) + | (WrapperKind::OptionRange, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { @@ -3371,6 +3979,23 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } + (WrapperKind::OptionRefCell, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpOptionRefCellType<'_, #name, #inner_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().map(|r| r.borrow()), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_ref().map(|r| r.borrow_mut()), + _ => None, + }, + ) + } + }); + } (WrapperKind::None, None) => { // Basic type tokens.extend(quote! { @@ -3451,8 +4076,8 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } Data::Union(_) => { return syn::Error::new(input_span, "Kp derive does not support unions") - .to_compile_error() - .into(); + .to_compile_error() + .into(); } }; @@ -3527,12 +4152,9 @@ pub fn derive_partial_keypaths(input: TokenStream) -> TokenStream { quote! { #(#calls),* } } Data::Union(_) => { - return syn::Error::new( - input.ident.span(), - "Pkp derive does not support unions", - ) - .to_compile_error() - .into(); + return syn::Error::new(input.ident.span(), "Pkp derive does not support unions") + .to_compile_error() + .into(); } }; @@ -3569,9 +4191,9 @@ pub fn derive_partial_keypaths(input: TokenStream) -> TokenStream { /// /// let kps = Person::any_kps(); /// assert_eq!(kps.len(), 2); -/// let person = Person { name: "Alice".into(), age: 30 }; +/// let person = Person { name: "Akash".into(), age: 30 }; /// let name: Option<&String> = kps[0].get(&person as &dyn std::any::Any).and_then(|v| v.downcast_ref()); -/// assert_eq!(name, Some(&"Alice".to_string())); +/// assert_eq!(name, Some(&"Akash".to_string())); /// ``` #[proc_macro_derive(Akp)] pub fn derive_any_keypaths(input: TokenStream) -> TokenStream { @@ -3615,12 +4237,9 @@ pub fn derive_any_keypaths(input: TokenStream) -> TokenStream { quote! { #(#calls),* } } Data::Union(_) => { - return syn::Error::new( - input.ident.span(), - "Akp derive does not support unions", - ) - .to_compile_error() - .into(); + return syn::Error::new(input.ident.span(), "Akp derive does not support unions") + .to_compile_error() + .into(); } }; diff --git a/key-paths-derive/tests/comprehensive_test.rs b/key-paths-derive/tests/comprehensive_test.rs index 1ed3034..01151e4 100644 --- a/key-paths-derive/tests/comprehensive_test.rs +++ b/key-paths-derive/tests/comprehensive_test.rs @@ -1,9 +1,9 @@ use key_paths_derive::Kp; -use rust_key_paths::{KpType, LockKp}; +use rust_key_paths::{KpTrait, KpType, LockKp}; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, VecDeque}; -use std::sync::atomic::{AtomicI32, AtomicU64, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicI32, AtomicU64, Ordering}; // Test collections #[derive(Kp)] @@ -168,7 +168,10 @@ fn test_cow_access() { assert_eq!(cow_owned_kp.get(&data).map(|s| s.as_str()), Some("owned")); let cow_borrowed_kp = WithCow::cow_borrowed(); - assert_eq!(cow_borrowed_kp.get(&data).map(|s| s.as_str()), Some("borrowed")); + assert_eq!( + cow_borrowed_kp.get(&data).map(|s| s.as_str()), + Some("borrowed") + ); let opt_cow_kp = WithCow::opt_cow(); assert_eq!(opt_cow_kp.get(&data).map(|s| s.as_str()), Some("optional")); @@ -183,12 +186,19 @@ fn test_cow_mutable() { }; let cow_owned_kp = WithCow::cow_owned(); - cow_owned_kp.get_mut(&mut data).map(|s| s.make_ascii_uppercase()); + cow_owned_kp + .get_mut(&mut data) + .map(|s| s.make_ascii_uppercase()); assert_eq!(data.cow_owned.as_str(), "ORIGINAL"); let opt_cow_kp = WithCow::opt_cow(); - opt_cow_kp.get_mut(&mut data).map(|s| s.make_ascii_uppercase()); - assert_eq!(data.opt_cow.as_ref().map(|c| c.as_str()), Some("OPT_ORIGINAL")); + opt_cow_kp + .get_mut(&mut data) + .map(|s| s.make_ascii_uppercase()); + assert_eq!( + data.opt_cow.as_ref().map(|c| c.as_str()), + Some("OPT_ORIGINAL") + ); } #[test] @@ -201,7 +211,10 @@ fn test_atomic_types() { let counter_kp = WithAtomics::counter(); let atomic = counter_kp.get(&data).unwrap(); assert_eq!(atomic.load(Ordering::SeqCst), 42); - counter_kp.get_mut(&mut data).unwrap().store(100, Ordering::SeqCst); + counter_kp + .get_mut(&mut data) + .unwrap() + .store(100, Ordering::SeqCst); assert_eq!(data.counter.load(Ordering::SeqCst), 100); let flags_kp = WithAtomics::flags(); @@ -247,7 +260,10 @@ fn test_hashmap_at() { let kp = WithMaps::users_at("alice".to_string()); assert_eq!(kp.get(&data), Some(&100)); - let mut data_mut = WithMaps { users, cache: BTreeMap::new() }; + let mut data_mut = WithMaps { + users, + cache: BTreeMap::new(), + }; let kp_mut = WithMaps::users_at("alice".to_string()); *kp_mut.get_mut(&mut data_mut).unwrap() = 150; assert_eq!(data_mut.users.get("alice"), Some(&150)); @@ -266,7 +282,10 @@ fn test_btreemap_at() { let kp = WithMaps::cache_at(1); assert_eq!(kp.get(&data), Some(&"one".to_string())); - let mut data_mut = WithMaps { users: HashMap::new(), cache }; + let mut data_mut = WithMaps { + users: HashMap::new(), + cache, + }; let kp_mut = WithMaps::cache_at(1); *kp_mut.get_mut(&mut data_mut).unwrap() = "1".to_string(); assert_eq!(data_mut.cache.get(&1), Some(&"1".to_string())); @@ -392,7 +411,7 @@ fn test_std_mutex_with_lockkp() { // rwlock_kp.get() // rwlock_kp.sync_get(&locks).unwrap(); // rwlock_kp.sync_get_mut() - + // Create LockKp for accessing the inner value let next: KpType = rust_key_paths::Kp::new(|i: &i32| Some(i), |i: &mut i32| Some(i)); diff --git a/key-paths-derive/tests/derive_test.rs b/key-paths-derive/tests/derive_test.rs index feae30e..dd35829 100644 --- a/key-paths-derive/tests/derive_test.rs +++ b/key-paths-derive/tests/derive_test.rs @@ -17,7 +17,7 @@ struct Company { #[test] fn test_basic_field_access() { let person = Person { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, email: "alice@example.com".to_string(), }; @@ -25,7 +25,7 @@ fn test_basic_field_access() { // Test accessing name field let name_kp = Person::name(); let name_value = name_kp.get(&person); - assert_eq!(name_value, Some(&"Alice".to_string())); + assert_eq!(name_value, Some(&"Akash".to_string())); // Test accessing age field let age_kp = Person::age(); @@ -65,7 +65,7 @@ fn test_keypath_composition() { name: "Tech Corp".to_string(), employees: vec![ Person { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, email: "alice@example.com".to_string(), }, @@ -89,7 +89,7 @@ fn test_keypath_composition() { let first_employee_kp = Company::employees_at(0); let first_employee = first_employee_kp.get(&company); - assert_eq!(first_employee.map(|e| &e.name), Some(&"Alice".to_string())); + assert_eq!(first_employee.map(|e| &e.name), Some(&"Akash".to_string())); } #[test] diff --git a/key-paths-derive/tests/enum_complex_test.rs b/key-paths-derive/tests/enum_complex_test.rs index 74b5cd1..c6eb40f 100644 --- a/key-paths-derive/tests/enum_complex_test.rs +++ b/key-paths-derive/tests/enum_complex_test.rs @@ -1,8 +1,8 @@ //! Test enum with complex containers like Arc> (reusing struct prior art) use key_paths_derive::Kp; -use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; +use std::sync::atomic::{AtomicI32, Ordering}; #[derive(Debug, Kp)] enum Message { @@ -45,7 +45,9 @@ fn test_enum_empty() { #[tokio::test] async fn test_enum_tokio_async() { - let msg = Message::TokioData(Arc::new(tokio::sync::RwLock::new("async_hello".to_string()))); + let msg = Message::TokioData(Arc::new(tokio::sync::RwLock::new( + "async_hello".to_string(), + ))); let arc_kp = Message::tokio_data(); let arc = arc_kp.get(&msg); assert!(arc.is_some()); @@ -65,7 +67,9 @@ fn test_enum_atomic() { #[test] fn test_enum_parking_lot() { - let msg = Message::ParkingData(Arc::new(parking_lot::RwLock::new("parking_hello".to_string()))); + let msg = Message::ParkingData(Arc::new(parking_lot::RwLock::new( + "parking_hello".to_string(), + ))); let arc_kp = Message::parking_data(); let arc = arc_kp.get(&msg); assert!(arc.is_some()); diff --git a/key-paths-derive/tests/pin_project_test.rs b/key-paths-derive/tests/pin_project_test.rs index 226a78e..04fd8ab 100644 --- a/key-paths-derive/tests/pin_project_test.rs +++ b/key-paths-derive/tests/pin_project_test.rs @@ -24,21 +24,30 @@ struct WithPinnedField { #[test] fn test_pinned_field_container_and_pinned_projection() { - let data = WithPinnedField { fair: true, value: 42 }; + let data = WithPinnedField { + fair: true, + value: 42, + }; // Regular container access let kp = WithPinnedField::value(); assert_eq!(kp.get(&data), Some(&42)); // Mutable via Kp - let mut data_mut = WithPinnedField { fair: false, value: 100 }; + let mut data_mut = WithPinnedField { + fair: false, + value: 100, + }; if let Some(v) = kp.get_mut(&mut data_mut) { *v = 200; } assert_eq!(data_mut.value, 200); // Pinned projection - requires Pin<&mut Self> - let mut data_pin = WithPinnedField { fair: true, value: 99 }; + let mut data_pin = WithPinnedField { + fair: true, + value: 99, + }; let pinned = Pin::new(&mut data_pin); let projected: Pin<&mut i32> = WithPinnedField::value_pinned(pinned); assert_eq!(*projected.get_mut(), 99); diff --git a/key-paths-iter/src/kp_gpu.rs b/key-paths-iter/src/kp_gpu.rs index 3fa5bcb..ba9c15a 100644 --- a/key-paths-iter/src/kp_gpu.rs +++ b/key-paths-iter/src/kp_gpu.rs @@ -28,7 +28,9 @@ mod sealed { /// Marker + helper for types that map to a WGSL scalar. /// /// Implement this for your own `#[repr(C)]` newtypes if needed. -pub trait GpuCompatible: sealed::Sealed + bytemuck::Pod + bytemuck::Zeroable + Copy + 'static { +pub trait GpuCompatible: + sealed::Sealed + bytemuck::Pod + bytemuck::Zeroable + Copy + 'static +{ /// WGSL type name: `"f32"`, `"u32"`, `"i32"`. fn wgsl_type() -> &'static str; } @@ -108,11 +110,7 @@ where { /// Compose: apply `next` kernel *after* `self` kernel. pub fn and_then_gpu(self, next_kernel: impl Into) -> GpuKp { - let combined = format!( - "{}\n {}", - self.kernel.wgsl_statement, - next_kernel.into() - ); + let combined = format!("{}\n {}", self.kernel.wgsl_statement, next_kernel.into()); GpuKp { kernel: GpuKernel::new(combined), extractor: self.extractor, @@ -166,10 +164,15 @@ where /// GPU extensions for [`KpType`]; mirrors `.map()` / `.filter()` but produces [`GpuKp`]. pub trait KpGpuExt { /// Lift `self` into a `GpuKp` with an identity (pass-through) kernel. - fn into_gpu(self) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync>; + fn into_gpu( + self, + ) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync>; /// Attach a WGSL element-wise transform, producing a `GpuKp` (static dispatch). - fn map_gpu(self, wgsl_statement: impl Into) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync>; + fn map_gpu( + self, + wgsl_statement: impl Into, + ) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync>; /// Run `self` across a `roots` slice: one GPU dispatch, transformed results. fn par_gpu( @@ -185,11 +188,16 @@ where R: 'static, V: GpuCompatible, { - fn into_gpu(self) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync> { + fn into_gpu( + self, + ) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync> { self.map_gpu("output[id] = input[id];") } - fn map_gpu(self, wgsl_statement: impl Into) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync> { + fn map_gpu( + self, + wgsl_statement: impl Into, + ) -> GpuKp Option + Send + Sync, impl Fn(&mut R, V) + Send + Sync> { let kp = Arc::new(self); GpuKp { extractor: { @@ -260,11 +268,7 @@ where /// Chain another WGSL statement. pub fn and_then_gpu(self, next_kernel: impl Into) -> GpuKpVec { - let combined = format!( - "{}\n {}", - self.kernel.wgsl_statement, - next_kernel.into() - ); + let combined = format!("{}\n {}", self.kernel.wgsl_statement, next_kernel.into()); GpuKpVec { kernel: GpuKernel::new(combined), extractor: self.extractor, @@ -278,7 +282,15 @@ where /// GPU extensions for keypaths whose **value type is `Vec`**. pub trait KpGpuVecExt { /// Attach an element-wise WGSL transform over the vector (static dispatch). - fn map_gpu_vec(self, wgsl_statement: impl Into) -> GpuKpVec Option> + Send + Sync, impl Fn(&mut R, Vec) + Send + Sync>; + fn map_gpu_vec( + self, + wgsl_statement: impl Into, + ) -> GpuKpVec< + R, + V, + impl Fn(&R) -> Option> + Send + Sync, + impl Fn(&mut R, Vec) + Send + Sync, + >; } impl KpGpuVecExt for KpType<'static, R, Vec> @@ -286,7 +298,15 @@ where R: 'static, V: GpuCompatible, { - fn map_gpu_vec(self, wgsl_statement: impl Into) -> GpuKpVec Option> + Send + Sync, impl Fn(&mut R, Vec) + Send + Sync> { + fn map_gpu_vec( + self, + wgsl_statement: impl Into, + ) -> GpuKpVec< + R, + V, + impl Fn(&R) -> Option> + Send + Sync, + impl Fn(&mut R, Vec) + Send + Sync, + > { let kp = Arc::new(self); GpuKpVec { extractor: { @@ -348,8 +368,7 @@ where (Some(a), Some(b)) => { let merged_kernel = format!( "if (id == 0u) {{ {} }} else {{ {} }}", - kp_a.kernel.wgsl_statement, - kp_b.kernel.wgsl_statement, + kp_a.kernel.wgsl_statement, kp_b.kernel.wgsl_statement, ); let merged = GpuKernel::new(merged_kernel); let results = ctx.dispatch_scalar::(&[a, b], &merged).ok(); @@ -640,49 +659,57 @@ fn main(@builtin(global_invocation_id) gid: vec3) {{ body = kernel.wgsl_statement, ); - let module = self.device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("gkp_shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(shader_src)), - }); + let module = self + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("gkp_shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(shader_src)), + }); - let bgl = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, + let bgl = self + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }, - ], - }); + ], + }); - let pipeline_layout = self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bgl], - push_constant_ranges: &[], - }); + let pipeline_layout = self + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); - let pipeline = self.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &module, - entry_point: "main", - }); + let pipeline = self + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &module, + entry_point: "main", + }); let readback_buf = self.device.create_buffer(&wgpu::BufferDescriptor { label: Some("gkp_rb"), @@ -701,11 +728,13 @@ fn main(@builtin(global_invocation_id) gid: vec3) {{ let chunk_size = (chunk_len * elem_size) as u64; let workgroups = (chunk_len as u32 + ws - 1) / ws; - let input_buf = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("gkp_in"), - contents: bytemuck::cast_slice(&values[offset..offset + chunk_len]), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); + let input_buf = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("gkp_in"), + contents: bytemuck::cast_slice(&values[offset..offset + chunk_len]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); let output_buf = self.device.create_buffer(&wgpu::BufferDescriptor { label: Some("gkp_out"), size: chunk_size, diff --git a/key-paths-iter/src/lib.rs b/key-paths-iter/src/lib.rs index 338958d..b622740 100644 --- a/key-paths-iter/src/lib.rs +++ b/key-paths-iter/src/lib.rs @@ -11,10 +11,10 @@ pub mod rayon_optimizations; #[cfg(feature = "rayon")] pub mod scale_par; -#[cfg(feature = "gpu")] -pub mod wgpu; #[cfg(feature = "gpu")] pub mod kp_gpu; +#[cfg(feature = "gpu")] +pub mod wgpu; /// Query builder for collection keypaths (KpType where value is `Vec`). pub struct CollectionQuery<'a, Root, Item> { @@ -243,7 +243,7 @@ mod tests { users: vec![ User { id: 1, - name: "Alice".into(), + name: "Akash".into(), age: 25, active: true, }, @@ -279,10 +279,14 @@ mod tests { assert_eq!(results[0].name, "Charlie"); // Check if any active user exists - assert!(QueryableCollection::query(&users_kp).filter(|u| u.active).exists(&db)); + assert!(QueryableCollection::query(&users_kp) + .filter(|u| u.active) + .exists(&db)); // Count active users - let count = QueryableCollection::query(&users_kp).filter(|u| u.active).count(&db); + let count = QueryableCollection::query(&users_kp) + .filter(|u| u.active) + .count(&db); assert_eq!(count, 3); } } diff --git a/key-paths-iter/src/query_par.rs b/key-paths-iter/src/query_par.rs index 43115ef..80eadfa 100644 --- a/key-paths-iter/src/query_par.rs +++ b/key-paths-iter/src/query_par.rs @@ -248,7 +248,8 @@ impl ParallelCollectionKeyPath F: Fn(&Item) -> bool + Sync + Send, Item: Sync, { - get_vec_static(self, root).and_then(|vec| vec.par_iter().find_first(|x| predicate(x)).map(|r| r)) + get_vec_static(self, root) + .and_then(|vec| vec.par_iter().find_first(|x| predicate(x)).map(|r| r)) } fn par_find_any<'a, F>(&self, root: &'a Root, predicate: F) -> Option<&'a Item> @@ -256,7 +257,8 @@ impl ParallelCollectionKeyPath F: Fn(&Item) -> bool + Sync + Send, Item: Sync, { - get_vec_static(self, root).and_then(|vec| vec.par_iter().find_any(|x| predicate(x)).map(|r| r)) + get_vec_static(self, root) + .and_then(|vec| vec.par_iter().find_any(|x| predicate(x)).map(|r| r)) } fn par_any(&self, root: &Root, predicate: F) -> bool diff --git a/key-paths-iter/src/rayon_optimizations.rs b/key-paths-iter/src/rayon_optimizations.rs index 33ef7a8..e971cc6 100644 --- a/key-paths-iter/src/rayon_optimizations.rs +++ b/key-paths-iter/src/rayon_optimizations.rs @@ -129,7 +129,10 @@ impl AdaptiveThreadPool { /// Get the pool selected for current load. pub fn get_pool(&self) -> Arc { - let idx = self.current_load.load(Ordering::Relaxed).min(self.pools.len() - 1); + let idx = self + .current_load + .load(Ordering::Relaxed) + .min(self.pools.len() - 1); Arc::clone(&self.pools[idx]) } @@ -422,17 +425,23 @@ impl OptimizationGuide { /// Memory-heavy (large allocations). pub fn scientific_computing() -> ThreadPool { - RayonConfig::memory_intensive().build().expect("thread pool") + RayonConfig::memory_intensive() + .build() + .expect("thread pool") } /// Low latency (games, trading). pub fn real_time() -> ThreadPool { - RayonConfig::latency_sensitive().build().expect("thread pool") + RayonConfig::latency_sensitive() + .build() + .expect("thread pool") } /// Physical cores only (e.g. training). pub fn machine_learning() -> ThreadPool { - RayonConfig::physical_cores_only().build().expect("thread pool") + RayonConfig::physical_cores_only() + .build() + .expect("thread pool") } } diff --git a/key-paths-iter/src/scale_par.rs b/key-paths-iter/src/scale_par.rs index cde656b..c846688 100644 --- a/key-paths-iter/src/scale_par.rs +++ b/key-paths-iter/src/scale_par.rs @@ -432,44 +432,47 @@ mod gpu_impl { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("HVM2 Reduction Shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/hvm_reduce.wgsl"))), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( + "../shaders/hvm_reduce.wgsl" + ))), }); - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("HVM2 Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("HVM2 Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }, - ], - }); + ], + }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("HVM2 Pipeline Layout"), @@ -477,12 +480,13 @@ mod gpu_impl { push_constant_ranges: &[], }); - let reduce_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("HVM2 Reduction Pipeline"), - layout: Some(&pipeline_layout), - module: &shader, - entry_point: "main", - }); + let reduce_pipeline = + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("HVM2 Reduction Pipeline"), + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); Ok(Self { device, @@ -502,24 +506,32 @@ mod gpu_impl { let nodes_bytes = bytemuck::cast_slice(&data.nodes); let pairs_bytes = bytemuck::cast_slice(&pairs_redo); - let nodes_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Nodes Buffer"), - contents: nodes_bytes, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, - }); + let nodes_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Nodes Buffer"), + contents: nodes_bytes, + usage: wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::COPY_SRC + | wgpu::BufferUsages::COPY_DST, + }); - let pairs_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Pairs Buffer"), - contents: pairs_bytes, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - }); + let pairs_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Pairs Buffer"), + contents: pairs_bytes, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + }); let metadata: [u32; 4] = [data.node_count, data.pairs.len() as u32, 0u32, 0u32]; - let metadata_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Metadata Buffer"), - contents: bytemuck::cast_slice(&metadata), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - }); + let metadata_buffer = + self.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Metadata Buffer"), + contents: bytemuck::cast_slice(&metadata), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + }); let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("HVM2 Bind Group"), @@ -540,9 +552,11 @@ mod gpu_impl { ], }); - let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("HVM2 Command Encoder"), - }); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("HVM2 Command Encoder"), + }); { let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { diff --git a/key-paths-iter/src/wgpu.rs b/key-paths-iter/src/wgpu.rs index 5cad169..f563bc0 100644 --- a/key-paths-iter/src/wgpu.rs +++ b/key-paths-iter/src/wgpu.rs @@ -166,11 +166,8 @@ impl AKpRunner { } fn run_numeric(&self, root: &dyn std::any::Any) -> Vec> { - let extracted: Vec> = self - .gpu_kps - .iter() - .map(|kp| (kp.extractor)(root)) - .collect(); + let extracted: Vec> = + self.gpu_kps.iter().map(|kp| (kp.extractor)(root)).collect(); let Some(ctx) = &self.wgpu_ctx else { return extracted; @@ -222,55 +219,65 @@ fn main(@builtin(global_invocation_id) gid: vec3) { } "#; - let module = ctx.device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("akp_shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(shader_src)), - }); - - let bind_group_layout = ctx.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("akp_bgl"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - let pipeline_layout = ctx.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("akp_pl"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let pipeline = ctx.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("akp_pipeline"), - layout: Some(&pipeline_layout), - module: &module, - entry_point: "main", - }); + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("akp_shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(shader_src)), + }); + + let bind_group_layout = + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("akp_bgl"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("akp_pl"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); - let input_buf = ctx.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("akp_input"), - contents: bytemuck::cast_slice(values), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("akp_pipeline"), + layout: Some(&pipeline_layout), + module: &module, + entry_point: "main", + }); + + let input_buf = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("akp_input"), + contents: bytemuck::cast_slice(values), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); let output_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { label: Some("akp_output"), size, diff --git a/key-paths-macros/src/lib.rs b/key-paths-macros/src/lib.rs index c037316..aee6afa 100644 --- a/key-paths-macros/src/lib.rs +++ b/key-paths-macros/src/lib.rs @@ -1,3 +1,64 @@ +//! Keypath macros and derive for keypath access. +//! +//! - **Declarative macros** `keypath!`, `get!`, `get_mut!`, `set!`, `get_or!`, `get_or_else!` +//! work with [rust-key-paths] `KpType` (use with [key-paths-derive] `Kp`). +//! - **Proc-macro** `#[derive(Keypath)]` for [key-paths-core]. + +// ─── Declarative macros for KpType (rust-key-paths) ───────────────────────── + +/// Build a keypath from `Type.field` segments. Use with types that have keypath accessors (e.g. `#[derive(Kp)]`). +#[macro_export] +macro_rules! keypath { + { $root:ident . $field:ident } => { $root::$field() }; + { $root:ident . $field:ident . $($ty:ident . $f:ident).+ } => { + $root::$field() $(.then($ty::$f()))+ + }; + ($root:ident . $field:ident) => { $root::$field() }; + ($root:ident . $field:ident . $($ty:ident . $f:ident).+) => { + $root::$field() $(.then($ty::$f()))+ + }; +} + +/// Shorthand for `keypath!(path).get(root)`. +#[macro_export] +macro_rules! get { + ($root:expr => $($path:tt)*) => { $crate::keypath!($($path)*).get($root) }; +} + +/// Shorthand for `keypath!(path).get_mut(root)`. +#[macro_export] +macro_rules! get_mut { + ($root:expr => $($path:tt)*) => { $crate::keypath!($($path)*).get_mut($root) }; +} + +/// Set value through keypath. Path in parentheses: `set!(root => (Type.field) = value)`. +#[macro_export] +macro_rules! set { + ($root:expr => ($($path:tt)*) = $value:expr) => { + $crate::keypath!($($path)*).get_mut($root).map(|x| *x = $value) + }; +} + +/// Get value at path or a default reference when the path returns `None`. +/// Returns `&T`. Use when you have a fallback reference: `get_or!(&root => Type.field, &default)`. +#[macro_export] +macro_rules! get_or { + ($root:expr => $($path:tt)*, $default:expr) => { + $crate::keypath!($($path)*).get($root).unwrap_or($default) + }; +} + +/// Get value at path, or compute an owned fallback when the path returns `None`. +/// Returns `T` (owned). Path value type must be `Clone`. Closure is only called when path is `None`. +#[macro_export] +macro_rules! get_or_else { + ($root:expr => $($path:tt)*, $closure:expr) => { + $crate::keypath!($($path)*).get($root).map(|r| r.clone()).unwrap_or_else($closure) + }; +} + +// ─── Proc-macro derive ───────────────────────────────────────────────────── + use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields, Type, parse_macro_input}; diff --git a/src/async_lock.rs b/src/async_lock.rs index 2d96454..50a32a6 100644 --- a/src/async_lock.rs +++ b/src/async_lock.rs @@ -29,7 +29,7 @@ //! - Only clones `PhantomData` which is zero-sized //! - Compiled away completely - zero runtime cost -use crate::Kp; +use crate::{Kp, KpTrait}; use async_trait::async_trait; use std::sync::Arc; @@ -150,25 +150,25 @@ where } impl< - R, - Lock, - Mid, - V, - Root, - LockValue, - MidValue, - Value, - MutRoot, - MutLock, - MutMid, - MutValue, - G1, - S1, - L, - G2, - S2, - > - SyncKeyPathLike for crate::lock::LockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, +> SyncKeyPathLike + for crate::lock::LockKp< R, Lock, Mid, @@ -413,6 +413,50 @@ where (self.next.set)(mid_value) } + /// Like [get](AsyncLockKp::get), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the getter. + #[inline] + pub async fn get_optional(&self, root: Option) -> Option + where + Lock: Clone, + { + match root { + Some(r) => self.get(r).await, + None => None, + } + } + + /// Like [get_mut](AsyncLockKp::get_mut), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the setter. + #[inline] + pub async fn get_mut_optional(&self, root: Option) -> Option + where + Lock: Clone, + { + match root { + Some(r) => self.get_mut(r).await, + None => None, + } + } + + /// Returns the value if the keypath succeeds (root is `Some` and get returns `Some`), otherwise calls `f` and returns its result. + #[inline] + pub async fn get_or_else(&self, root: Option, f: F) -> Value + where + Lock: Clone, + F: FnOnce() -> Value, + { + self.get_optional(root).await.unwrap_or_else(f) + } + + /// Returns the mutable value if the keypath succeeds (root is `Some` and get_mut returns `Some`), otherwise calls `f` and returns its result. + #[inline] + pub async fn get_mut_or_else(&self, root: Option, f: F) -> MutValue + where + Lock: Clone, + F: FnOnce() -> MutValue, + { + self.get_mut_optional(root).await.unwrap_or_else(f) + } + /// Set the value through the lock using an updater function. /// /// Uses interior mutability—no mutable root required. RwLock/Mutex allow mutation through @@ -480,8 +524,58 @@ where G1, S1, L, - impl Fn(MidValue) -> Option + Clone + use, - impl Fn(MutMid) -> Option + Clone + use, + impl Fn(MidValue) -> Option + + Clone + + use< + G1, + G2, + G3, + L, + Lock, + LockValue, + Mid, + MidValue, + MutLock, + MutMid, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S3, + Value, + Value2, + V, + V2, + >, + impl Fn(MutMid) -> Option + + Clone + + use< + G1, + G2, + G3, + L, + Lock, + LockValue, + Mid, + MidValue, + MutLock, + MutMid, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S3, + Value, + Value2, + V, + V2, + >, > where V: 'static, @@ -719,7 +813,25 @@ impl< G2, S2, > AsyncKeyPathLike - for AsyncLockKp + for AsyncLockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + > where Root: std::borrow::Borrow, LockValue: std::borrow::Borrow, @@ -871,7 +983,16 @@ where pub fn then( self, next_kp: crate::Kp, - ) -> AsyncKeyPathThenKp> + ) -> AsyncKeyPathThenKp< + R, + V3, + Root, + Value3, + MutRoot, + MutValue3, + Self, + crate::Kp, + > where V2: 'static, V3: 'static, @@ -907,8 +1028,53 @@ where S3_2, >( self, - lock_kp: crate::lock::LockKp, - ) -> AsyncLockKpThenLockKp> + lock_kp: crate::lock::LockKp< + V2, + Lock3, + Mid3, + V3, + Value2, + LockValue3, + MidValue3, + Value3, + MutValue2, + MutLock3, + MutMid3, + MutValue3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, + ) -> AsyncLockKpThenLockKp< + R, + V3, + Root, + Value3, + MutRoot, + MutValue3, + Self, + crate::lock::LockKp< + V2, + Lock3, + Mid3, + V3, + Value2, + LockValue3, + MidValue3, + Value3, + MutValue2, + MutLock3, + MutMid3, + MutValue3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, + > where V2: 'static, V3: 'static, @@ -936,10 +1102,23 @@ where /// Keypath that chains a sync keypath ([crate::Kp]) with an [AsyncKeyPathLike]. Use [crate::Kp::then_async] to create; then `.get(&root).await`. #[derive(Clone)] -pub struct KpThenAsyncKeyPath { +pub struct KpThenAsyncKeyPath< + R, + V, + V2, + Root, + Value, + Value2, + MutRoot, + MutValue, + MutValue2, + First, + Second, +> { pub(crate) first: First, pub(crate) second: Second, - pub(crate) _p: std::marker::PhantomData<(R, V, V2, Root, Value, Value2, MutRoot, MutValue, MutValue2)>, + pub(crate) _p: + std::marker::PhantomData<(R, V, V2, Root, Value, Value2, MutRoot, MutValue, MutValue2)>, } impl @@ -964,7 +1143,20 @@ where #[async_trait(?Send)] impl - AsyncKeyPathLike for KpThenAsyncKeyPath + AsyncKeyPathLike + for KpThenAsyncKeyPath< + R, + V, + V2, + Root, + Value, + Value2, + MutRoot, + MutValue, + MutValue2, + First, + Second, + > where First: SyncKeyPathLike, Second: AsyncKeyPathLike, @@ -991,7 +1183,16 @@ where pub fn then( self, next_kp: crate::Kp, - ) -> AsyncKeyPathThenKp> + ) -> AsyncKeyPathThenKp< + R, + V3, + Root, + Value3, + MutRoot, + MutValue3, + Self, + crate::Kp, + > where V3: 'static, Value2: std::borrow::Borrow, @@ -1019,7 +1220,16 @@ pub struct AsyncKeyPathThenKp and Kp). impl - AsyncKeyPathThenKp> + AsyncKeyPathThenKp< + R, + V2, + Root, + Value2, + MutRoot, + MutValue2, + First, + crate::Kp, + > where First: AsyncKeyPathLike, First::Value: std::borrow::Borrow, @@ -1044,8 +1254,8 @@ where } #[async_trait(?Send)] -impl - AsyncKeyPathLike for ComposedAsyncLockKp +impl AsyncKeyPathLike + for ComposedAsyncLockKp where First: AsyncKeyPathLike, Second: AsyncKeyPathLike, @@ -1074,38 +1284,38 @@ pub struct AsyncLockKpThenLockKp + R, + V2, + Root, + Value2, + MutRoot, + MutValue2, + Lock, + Mid, + V, + LockValue, + MidValue, + Value, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + Lock2, + Mid2, + LockValue2, + MidValue2, + MutLock2, + MutMid2, + G2_1, + S2_1, + L2, + G2_2, + S2_2, +> AsyncLockKpThenLockKp< R, V2, @@ -1113,8 +1323,44 @@ impl< Value2, MutRoot, MutValue2, - AsyncLockKp, - crate::lock::LockKp, + AsyncLockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + >, + crate::lock::LockKp< + V, + Lock2, + Mid2, + V2, + Value, + LockValue2, + MidValue2, + Value2, + MutValue, + MutLock2, + MutMid2, + MutValue2, + G2_1, + S2_1, + L2, + G2_2, + S2_2, + >, > where Root: std::borrow::Borrow, @@ -1160,26 +1406,26 @@ where // AsyncLockKpThenLockKp when First is ComposedAsyncLockKp (so ComposedAsyncLockKp::then_lock works). impl< - R, - V2, - Root, - Value2, - MutRoot, - MutValue2, - Lock3, - Mid3, - LockValue3, - MidValue3, - MutLock3, - MutMid3, - G3_1, - S3_1, - L3, - G3_2, - S3_2, - First, - Second, - > + R, + V2, + Root, + Value2, + MutRoot, + MutValue2, + Lock3, + Mid3, + LockValue3, + MidValue3, + MutLock3, + MutMid3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + First, + Second, +> AsyncLockKpThenLockKp< R, V2, @@ -1188,7 +1434,25 @@ impl< MutRoot, MutValue2, ComposedAsyncLockKp, - crate::lock::LockKp, + crate::lock::LockKp< + Value2, + Lock3, + Mid3, + V2, + Value2, + LockValue3, + MidValue3, + Value2, + MutValue2, + MutLock3, + MutMid3, + MutValue2, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, > where First: AsyncKeyPathLike, @@ -1221,26 +1485,26 @@ where // AsyncLockKpThenLockKp when First is AsyncLockKpThenLockKp (nested; enables .then_lock().then_lock() chains). impl< - R, - V2, - Root, - Value2, - MutRoot, - MutValue2, - Lock3, - Mid3, - LockValue3, - MidValue3, - MutLock3, - MutMid3, - G3_1, - S3_1, - L3, - G3_2, - S3_2, - F, - S, - > + R, + V2, + Root, + Value2, + MutRoot, + MutValue2, + Lock3, + Mid3, + LockValue3, + MidValue3, + MutLock3, + MutMid3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + F, + S, +> AsyncLockKpThenLockKp< R, V2, @@ -1249,7 +1513,25 @@ impl< MutRoot, MutValue2, AsyncLockKpThenLockKp, - crate::lock::LockKp, + crate::lock::LockKp< + Value2, + Lock3, + Mid3, + V2, + Value2, + LockValue3, + MidValue3, + Value2, + MutValue2, + MutLock3, + MutMid3, + MutValue2, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, > where F: AsyncKeyPathLike, @@ -1301,7 +1583,8 @@ where } // then_lock and then on AsyncLockKpThenLockKp so chains can continue. -impl AsyncLockKpThenLockKp +impl + AsyncLockKpThenLockKp where First: AsyncKeyPathLike, { @@ -1414,8 +1697,53 @@ where S3_2, >( self, - lock_kp: crate::lock::LockKp, - ) -> AsyncLockKpThenLockKp> + lock_kp: crate::lock::LockKp< + Value2, + Lock3, + Mid3, + V3, + Value2, + LockValue3, + MidValue3, + Value3, + MutValue2, + MutLock3, + MutMid3, + MutValue3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, + ) -> AsyncLockKpThenLockKp< + R, + V3, + Root, + Value3, + MutRoot, + MutValue3, + Self, + crate::lock::LockKp< + Value2, + Lock3, + Mid3, + V3, + Value2, + LockValue3, + MidValue3, + Value3, + MutValue2, + MutLock3, + MutMid3, + MutValue3, + G3_1, + S3_1, + L3, + G3_2, + S3_2, + >, + > where V3: 'static, Value2: std::borrow::Borrow, @@ -1443,7 +1771,16 @@ where pub fn then( self, next_kp: crate::Kp, - ) -> AsyncKeyPathThenKp> + ) -> AsyncKeyPathThenKp< + R, + V3, + Root, + Value3, + MutRoot, + MutValue3, + Self, + crate::Kp, + > where V3: 'static, Value2: std::borrow::Borrow, @@ -1708,6 +2045,49 @@ mod tests { assert_eq!(value.unwrap(), &"hello".to_string()); } + #[tokio::test] + async fn test_async_lock_kp_get_optional_or_else() { + use tokio::sync::Mutex; + + #[derive(Clone)] + struct Root { + data: Arc>, + } + + let mut root = Root { + data: Arc::new(Mutex::new(42)), + }; + + let lock_kp = { + let prev: KpType>> = + Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); + let next: KpType = Kp::new(|n: &i32| Some(n), |n: &mut i32| Some(n)); + AsyncLockKp::new(prev, TokioMutexAccess::new(), next) + }; + + // get_optional + assert!(lock_kp.get_optional(None).await.is_none()); + assert_eq!(lock_kp.get_optional(Some(&root)).await, Some(&42)); + + // get_mut_optional + assert!(lock_kp.get_mut_optional(None).await.is_none()); + if let Some(m) = lock_kp.get_mut_optional(Some(&mut root)).await { + *m = 99; + } + assert_eq!(lock_kp.get(&root).await, Some(&99)); + + // get_or_else + assert_eq!(*lock_kp.get_or_else(None, || &0).await, 0); + assert_eq!(*lock_kp.get_or_else(Some(&root), || &0).await, 99); + + // get_mut_or_else + let m = lock_kp + .get_mut_or_else(Some(&mut root), || panic!("unexpected")) + .await; + *m = 100; + assert_eq!(lock_kp.get(&root).await, Some(&100)); + } + #[tokio::test] async fn test_async_lock_kp_tokio_rwlock_basic() { use tokio::sync::RwLock; @@ -1757,28 +2137,17 @@ mod tests { AsyncLockKp::new(prev, TokioRwLockAccess::new(), next) }; - // Spawn multiple concurrent async reads - let mut handles = vec![]; - for _ in 0..10 { - let root_clone = root.clone(); - - // Re-create lock_kp for each task since we can't clone it easily - let lock_kp_for_task = { - let prev: KpType>> = - Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); - let next: KpType = Kp::new(|n: &i32| Some(n), |n: &mut i32| Some(n)); - AsyncLockKp::new(prev, TokioRwLockAccess::new(), next) - }; - - let handle = tokio::spawn(async move { lock_kp_for_task.get(&root_clone).await }); - handles.push(handle); - } - - // All reads should succeed - for handle in handles { - let result = handle.await.unwrap(); - assert_eq!(result, Some(&42)); - } + // Concurrent async reads in the same task (spawn would require 'static future; + // get() returns references so we use join! instead) + let lock_kp2 = { + let prev: KpType>> = + Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); + let next: KpType = Kp::new(|n: &i32| Some(n), |n: &mut i32| Some(n)); + AsyncLockKp::new(prev, TokioRwLockAccess::new(), next) + }; + let (a, b) = tokio::join!(lock_kp.get(&root), lock_kp2.get(&root)); + assert_eq!(a, Some(&42)); + assert_eq!(b, Some(&42)); // Test the original lock_kp as well let value = lock_kp.get(&root).await; diff --git a/src/lib.rs b/src/lib.rs index f3b84e3..8c8a739 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ use std::sync::{Arc, Mutex}; // Export the lock module pub mod lock; +pub mod prelude; + pub use lock::{ ArcMutexAccess, ArcRwLockAccess, LockAccess, LockKp, LockKpType, RcRefCellAccess, StdMutexAccess, StdRwLockAccess, @@ -28,8 +30,6 @@ pub use lock::{ // Export the async_lock module pub mod async_lock; - - // pub struct KpStatic { // pub get: fn(&R) -> Option<&V>, // pub set: fn(&mut R) -> Option<&mut V>, @@ -72,8 +72,6 @@ pub mod async_lock; // pub static STATIC_STR_FIELD_KP: KpStatic = // KpStatic::new(__get_static_str_field, __set_static_str_field); - - #[cfg(feature = "pin_project")] pub mod pin; @@ -90,6 +88,161 @@ impl KeyPathValueTarget for &mut T { type Target = T; } +/// Build a keypath from `Type.field` segments. Use with types that have keypath accessors (e.g. `#[derive(Kp)]` from key-paths-derive). +#[macro_export] +macro_rules! keypath { + { $root:ident . $field:ident } => { $root::$field() }; + { $root:ident . $field:ident . $($ty:ident . $f:ident).+ } => { + $root::$field() $(.then($ty::$f()))+ + }; + ($root:ident . $field:ident) => { $root::$field() }; + ($root:ident . $field:ident . $($ty:ident . $f:ident).+) => { + $root::$field() $(.then($ty::$f()))+ + }; +} + +/// Get value through a keypath or a default reference when the path returns `None`. +/// Use with `KpType`: `get_or!(User::name(), &user, &default)` where `default` is `&T` (same type as the path value). Returns `&T`. +/// Path syntax: `get_or!(&user => User.name, &default)`. +#[macro_export] +macro_rules! get_or { + ($kp:expr, $root:expr, $default:expr) => { + $kp.get($root).unwrap_or($default) + }; + ($root:expr => $($path:tt)*, $default:expr) => { + $crate::get_or!($crate::keypath!($($path)*), $root, $default) + }; +} + +/// Get value through a keypath, or compute an owned fallback when the path returns `None`. +/// Use with `KpType`: `get_or_else!(User::name(), &user, || "default".to_string())`. +/// Returns `T` (owned). The keypath's value type must be `Clone`. The closure is only called when the path is `None`. +/// Path syntax: `get_or_else!(&user => (User.name), || "default".to_string())` — path in parentheses. +#[macro_export] +macro_rules! get_or_else { + ($kp:expr, $root:expr, $closure:expr) => { + $kp.get($root).map(|r| r.clone()).unwrap_or_else($closure) + }; + ($root:expr => ($($path:tt)*), $closure:expr) => { + $crate::get_or_else!($crate::keypath!($($path)*), $root, $closure) + }; +} + +/// Zip multiple keypaths on the same root and apply a closure to the tuple of values. +/// Returns `Some(closure((v1, v2, ...)))` when all keypaths succeed, else `None`. +/// +/// # Example +/// ``` +/// use rust_key_paths::{Kp, KpType, zip_with_kp}; +/// struct User { name: String, age: u32, city: String } +/// let name_kp = KpType::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); +/// let age_kp = KpType::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); +/// let city_kp = KpType::new(|u: &User| Some(&u.city), |u: &mut User| Some(&mut u.city)); +/// let user = User { name: "Akash".into(), age: 30, city: "NYC".into() }; +/// let summary = zip_with_kp!( +/// &user, +/// |(name, age, city)| format!("{}, {} from {}", name, age, city) => +/// name_kp, +/// age_kp, +/// city_kp +/// ); +/// assert_eq!(summary, Some("Akash, 30 from NYC".to_string())); +/// ``` +#[macro_export] +macro_rules! zip_with_kp { + ($root:expr, $closure:expr => $kp1:expr, $kp2:expr) => { + match ($kp1.get($root), $kp2.get($root)) { + (Some(__a), Some(__b)) => Some($closure((__a, __b))), + _ => None, + } + }; + ($root:expr, $closure:expr => $kp1:expr, $kp2:expr, $kp3:expr) => { + match ($kp1.get($root), $kp2.get($root), $kp3.get($root)) { + (Some(__a), Some(__b), Some(__c)) => Some($closure((__a, __b, __c))), + _ => None, + } + }; + ($root:expr, $closure:expr => $kp1:expr, $kp2:expr, $kp3:expr, $kp4:expr) => { + match ( + $kp1.get($root), + $kp2.get($root), + $kp3.get($root), + $kp4.get($root), + ) { + (Some(__a), Some(__b), Some(__c), Some(__d)) => Some($closure((__a, __b, __c, __d))), + _ => None, + } + }; + ($root:expr, $closure:expr => $kp1:expr, $kp2:expr, $kp3:expr, $kp4:expr, $kp5:expr) => { + match ( + $kp1.get($root), + $kp2.get($root), + $kp3.get($root), + $kp4.get($root), + $kp5.get($root), + ) { + (Some(__a), Some(__b), Some(__c), Some(__d), Some(__e)) => { + Some($closure((__a, __b, __c, __d, __e))) + } + _ => None, + } + }; + ($root:expr, $closure:expr => $kp1:expr, $kp2:expr, $kp3:expr, $kp4:expr, $kp5:expr, $kp6:expr) => { + match ( + $kp1.get($root), + $kp2.get($root), + $kp3.get($root), + $kp4.get($root), + $kp5.get($root), + $kp6.get($root), + ) { + (Some(__a), Some(__b), Some(__c), Some(__d), Some(__e), Some(__f)) => { + Some($closure((__a, __b, __c, __d, __e, __f))) + } + _ => None, + } + }; +} + +/// Kp will force dev to create get and set while value will be owned +pub type KpValue<'a, R, V> = Kp< + R, + V, + &'a R, + V, // Returns owned V, not &V + &'a mut R, + V, // Returns owned V, not &mut V + for<'b> fn(&'b R) -> Option, + for<'b> fn(&'b mut R) -> Option, +>; + +/// Kp will force dev to create get and set while root and value both will be owned +pub type KpOwned = Kp< + R, + V, + R, + V, // Returns owned V, not &V + R, + V, // Returns owned V, not &mut V + fn(R) -> Option, + fn(R) -> Option, +>; + +/// Kp will force dev to create get and set while taking full ownership of the Root while returning Root as value. +pub type KpRoot = Kp< + R, + R, + R, + R, // Returns owned V, not &V + R, + R, // Returns owned V, not &mut V + fn(R) -> Option, + fn(R) -> Option, +>; + +/// Kp for void - experimental +pub type KpVoid = Kp<(), (), (), (), (), (), fn() -> Option<()>, fn() -> Option<()>>; + pub type KpDynamic = Kp< R, V, @@ -101,6 +254,28 @@ pub type KpDynamic = Kp< Box Fn(&'a mut R) -> Option<&'a mut V> + Send + Sync>, >; +pub type KpBox<'a, R, V> = Kp< + R, + V, + &'a R, + &'a V, + &'a mut R, + &'a mut V, + Box Option<&'a V> + 'a>, + Box Option<&'a mut V> + 'a>, +>; + +pub type KpArc<'a, R, V> = Kp< + R, + V, + &'a R, + &'a V, + &'a mut R, + &'a mut V, + Arc Option<&'a V> + Send + Sync + 'a>, + Arc Option<&'a mut V> + Send + Sync + 'a>, +>; + pub type KpType<'a, R, V> = Kp< R, V, @@ -112,6 +287,30 @@ pub type KpType<'a, R, V> = Kp< for<'b> fn(&'b mut R) -> Option<&'b mut V>, >; +pub type KpTraitType<'a, R, V> = dyn KpTrait< + R, + V, + &'a R, + &'a V, + &'a mut R, + &'a mut V, + for<'b> fn(&'b R) -> Option<&'b V>, + for<'b> fn(&'b mut R) -> Option<&'b mut V>, + >; + +/// Keypath for `Option>`: `get` returns `Option>` so the caller holds the guard. +/// Use `.get(root).as_ref().map(std::cell::Ref::deref)` to get `Option<&V>` while the `Ref` is in scope. +pub type KpOptionRefCellType<'a, R, V> = Kp< + R, + V, + &'a R, + std::cell::Ref<'a, V>, + &'a mut R, + std::cell::RefMut<'a, V>, + for<'b> fn(&'b R) -> Option>, + for<'b> fn(&'b mut R) -> Option>, +>; + impl<'a, R, V> KpType<'a, R, V> where 'a: 'static, @@ -187,16 +386,18 @@ pub type KpComposed = Kp< Box Fn(&'b mut R) -> Option<&'b mut V> + Send + Sync>, >; -impl Kp< - R, - V, - &'static R, - &'static V, - &'static mut R, - &'static mut V, - Box Fn(&'b R) -> Option<&'b V> + Send + Sync>, - Box Fn(&'b mut R) -> Option<&'b mut V> + Send + Sync>, -> { +impl + Kp< + R, + V, + &'static R, + &'static V, + &'static mut R, + &'static mut V, + Box Fn(&'b R) -> Option<&'b V> + Send + Sync>, + Box Fn(&'b mut R) -> Option<&'b mut V> + Send + Sync>, + > +{ /// Build a keypath from two closures (e.g. when they capture a variable like an index). /// Same pattern as `Kp::new` in lock.rs; use this when the keypath captures variables. pub fn from_closures(get: G, set: S) -> Self @@ -401,7 +602,7 @@ impl AKp { /// ``` /// use rust_key_paths::{AKp, Kp, KpType}; /// struct User { name: String } - /// let user = User { name: "Alice".to_string() }; + /// let user = User { name: "Akash".to_string() }; /// let name_kp = KpType::new(|u: &User| Some(&u.name), |_| None); /// let name_akp = AKp::new(name_kp); /// let len_akp = name_akp.map::(|s| s.len()); @@ -618,7 +819,7 @@ where /// ``` /// use rust_key_paths::{Kp, KpType, PKp}; /// struct User { name: String } - /// let user = User { name: "Alice".to_string() }; + /// let user = User { name: "Akash".to_string() }; /// let name_kp = KpType::new(|u: &User| Some(&u.name), |_| None); /// let name_pkp = PKp::new(name_kp); /// let len_pkp = name_pkp.map::(|s| s.len()); @@ -698,80 +899,22 @@ where // ========== ANY KEYPATHS (Hide Both Root and Value Types) ========== -/// AKp (AnyKeyPath) - Hides both Root and Value types -/// Most flexible keypath type for heterogeneous collections -/// Uses dynamic dispatch and type checking at runtime -/// -/// # Mutation: get vs get_mut (setter path) -/// -/// - **[get](Kp::get)** uses the `get` closure (getter): `Fn(Root) -> Option` -/// - **[get_mut](Kp::get_mut)** uses the `set` closure (setter): `Fn(MutRoot) -> Option` -/// -/// When mutating through a Kp, the **setter path** is used—`get_mut` invokes the `set` closure, -/// not the `get` closure. The getter is for read-only access only. -#[derive(Clone)] -pub struct Kp -where - Root: std::borrow::Borrow, - MutRoot: std::borrow::BorrowMut, - MutValue: std::borrow::BorrowMut, - G: Fn(Root) -> Option, - S: Fn(MutRoot) -> Option, -{ - /// Getter closure: used by [Kp::get] for read-only access. - pub(crate) get: G, - /// Setter closure: used by [Kp::get_mut] for mutation. - pub(crate) set: S, - _p: std::marker::PhantomData<(R, V, Root, Value, MutRoot, MutValue)>, -} - -// Kp is a functional component (get/set) with no owned data; Send/Sync follow from G and S. -unsafe impl Send for Kp -where - Root: std::borrow::Borrow, - MutRoot: std::borrow::BorrowMut, - MutValue: std::borrow::BorrowMut, - G: Fn(Root) -> Option + Send, - S: Fn(MutRoot) -> Option + Send, -{ -} -unsafe impl Sync for Kp -where - Root: std::borrow::Borrow, - MutRoot: std::borrow::BorrowMut, - MutValue: std::borrow::BorrowMut, - G: Fn(Root) -> Option + Sync, - S: Fn(MutRoot) -> Option + Sync, -{ -} - -impl Kp -where - Root: std::borrow::Borrow, - Value: std::borrow::Borrow, - MutRoot: std::borrow::BorrowMut, - MutValue: std::borrow::BorrowMut, - G: Fn(Root) -> Option, - S: Fn(MutRoot) -> Option, -{ - pub fn new(get: G, set: S) -> Self { - Self { - get: get, - set: set, - _p: std::marker::PhantomData, - } - } - - #[inline] - pub fn get(&self, root: Root) -> Option { - (self.get)(root) +pub trait KpTrait { + fn type_id_of_root() -> TypeId + where + R: 'static, + { + std::any::TypeId::of::() } - #[inline] - pub fn get_mut(&self, root: MutRoot) -> Option { - (self.set)(root) + fn type_id_of_value() -> TypeId + where + V: 'static, + { + std::any::TypeId::of::() } - - pub fn then( + fn get(&self, root: Root) -> Option; + fn get_mut(&self, root: MutRoot) -> Option; + fn then( self, next: Kp, ) -> Kp< @@ -781,24 +924,25 @@ where SubValue, MutRoot, MutSubValue, - impl Fn(Root) -> Option + use, - impl Fn(MutRoot) -> Option + use, + impl Fn(Root) -> Option, + impl Fn(MutRoot) -> Option, > where + Self: Sized, + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, SubValue: std::borrow::Borrow, MutSubValue: std::borrow::BorrowMut, G2: Fn(Value) -> Option, S2: Fn(MutValue) -> Option, - V: 'static, - { - Kp::new( - move |root: Root| (self.get)(root).and_then(|value| (next.get)(value)), - move |root: MutRoot| (self.set)(root).and_then(|value| (next.set)(value)), - ) - } + V: 'static; +} +pub trait ChainExt { /// Chain with a sync [crate::lock::LockKp]. Use `.get(root)` / `.get_mut(root)` on the returned keypath. - pub fn then_lock< + fn then_lock< Lock, Mid, V2, @@ -834,7 +978,37 @@ where G2, S2, >, - ) -> crate::lock::KpThenLockKp> + ) -> crate::lock::KpThenLockKp< + R, + V, + V2, + Root, + Value, + Value2, + MutRoot, + MutValue, + MutValue2, + Self, + crate::lock::LockKp< + V, + Lock, + Mid, + V2, + Value, + LockValue, + MidValue, + Value2, + MutValue, + MutLock, + MutMid, + MutValue2, + G1, + S1, + L, + G2, + S2, + >, + > where V: 'static + Clone, V2: 'static, @@ -851,31 +1025,158 @@ where L: crate::lock::LockAccess + crate::lock::LockAccess, G2: Fn(MidValue) -> Option, S2: Fn(MutMid) -> Option, - { - crate::lock::KpThenLockKp { - first: self, - second: lock_kp, - _p: std::marker::PhantomData, - } - } + Self: Sized; /// Chain with a #[pin] Future field await (pin_project pattern). Use `.get_mut(&mut root).await` on the returned keypath. - /// Enables composing futures: `kp.then_pin_future(S::fut_pin_future_kp()).then(...)` to go deeper. #[cfg(feature = "pin_project")] - pub fn then_pin_future( + fn then_pin_future( self, pin_fut: L, - ) -> crate::pin::KpThenPinFuture< + ) -> crate::pin::KpThenPinFuture + where + V: 'static, + Struct: Unpin + 'static, + Output: 'static, + Value: std::borrow::Borrow, + MutValue: std::borrow::BorrowMut, + L: crate::pin::PinFutureAwaitLike + Sync, + Self: Sized; + + /// Chain with an async keypath (e.g. [crate::async_lock::AsyncLockKp]). Use `.get(&root).await` on the returned keypath. + fn then_async( + self, + async_kp: AsyncKp, + ) -> crate::async_lock::KpThenAsyncKeyPath< R, - Struct, - Output, + V, + ::Target, Root, - MutRoot, Value, + AsyncKp::Value, + MutRoot, MutValue, + AsyncKp::MutValue, Self, + AsyncKp, + > + where + V: 'static, + Value: std::borrow::Borrow, + MutValue: std::borrow::BorrowMut, + AsyncKp: crate::async_lock::AsyncKeyPathLike, + AsyncKp::Value: KeyPathValueTarget + + std::borrow::Borrow<::Target>, + AsyncKp::MutValue: std::borrow::BorrowMut<::Target>, + ::Target: 'static, + Self: Sized; +} + +impl ChainExt + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + fn then_lock< + Lock, + Mid, + V2, + LockValue, + MidValue, + Value2, + MutLock, + MutMid, + MutValue2, + G1, + S1, L, + G2, + S2, + >( + self, + lock_kp: crate::lock::LockKp< + V, + Lock, + Mid, + V2, + Value, + LockValue, + MidValue, + Value2, + MutValue, + MutLock, + MutMid, + MutValue2, + G1, + S1, + L, + G2, + S2, + >, + ) -> crate::lock::KpThenLockKp< + R, + V, + V2, + Root, + Value, + Value2, + MutRoot, + MutValue, + MutValue2, + Self, + crate::lock::LockKp< + V, + Lock, + Mid, + V2, + Value, + LockValue, + MidValue, + Value2, + MutValue, + MutLock, + MutMid, + MutValue2, + G1, + S1, + L, + G2, + S2, + >, > + where + V: 'static + Clone, + V2: 'static, + Value: std::borrow::Borrow, + Value2: std::borrow::Borrow, + MutValue: std::borrow::BorrowMut, + MutValue2: std::borrow::BorrowMut, + LockValue: std::borrow::Borrow, + MidValue: std::borrow::Borrow, + MutLock: std::borrow::BorrowMut, + MutMid: std::borrow::BorrowMut, + G1: Fn(Value) -> Option, + S1: Fn(MutValue) -> Option, + L: crate::lock::LockAccess + crate::lock::LockAccess, + G2: Fn(MidValue) -> Option, + S2: Fn(MutMid) -> Option, + { + crate::lock::KpThenLockKp { + first: self, + second: lock_kp, + _p: std::marker::PhantomData, + } + } + + #[cfg(feature = "pin_project")] + fn then_pin_future( + self, + pin_fut: L, + ) -> crate::pin::KpThenPinFuture where V: 'static, Struct: Unpin + 'static, @@ -891,9 +1192,7 @@ where } } - /// Chain with an async keypath (e.g. [crate::async_lock::AsyncLockKp]). Use `.get(&root).await` on the returned keypath. - /// When `AsyncKp::Value` is a reference type (`&T` / `&mut T`), `V2` is inferred as `T` via [KeyPathValueTarget]. - pub fn then_async( + fn then_async( self, async_kp: AsyncKp, ) -> crate::async_lock::KpThenAsyncKeyPath< @@ -925,20 +1224,174 @@ where _p: std::marker::PhantomData, } } +} - /// Map the value through a transformation function - /// Returns a new keypath that transforms the value when accessed - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { name: String } - /// let user = User { name: "Alice".to_string() }; - /// let name_kp = KpType::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); - /// let len_kp = name_kp.map(|name: &String| name.len()); - /// assert_eq!(len_kp.get(&user), Some(5)); - /// ``` - pub fn map( +pub trait AccessorTrait: + KpTrait +{ + /// Like [get](Kp::get), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the getter. + #[inline] + fn get_optional(&self, root: Option) -> Option { + root.and_then(|r| self.get(r)) + } + + /// Like [get_mut](Kp::get_mut), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the setter. + #[inline] + fn get_mut_optional(&self, root: Option) -> Option { + root.and_then(|r| self.get_mut(r)) + } + + /// Returns the value if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + fn get_or_else(&self, root: Root, f: F) -> Value + where + F: FnOnce() -> Value, + { + self.get(root).unwrap_or_else(f) + } + + /// Returns the mutable value if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + fn get_mut_or_else(&self, root: MutRoot, f: F) -> MutValue + where + F: FnOnce() -> MutValue, + { + self.get_mut(root).unwrap_or_else(f) + } +} + +pub trait CoercionTrait: + KpTrait +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + fn for_arc<'b>( + &self, + ) -> Kp< + std::sync::Arc, + V, + std::sync::Arc, + Value, + std::sync::Arc, + MutValue, + impl Fn(std::sync::Arc) -> Option, + impl Fn(std::sync::Arc) -> Option, + > + where + R: 'b, + V: 'b, + Root: for<'a> From<&'a R>, + MutRoot: for<'a> From<&'a mut R>, + { + Kp::new( + move |arc_root: std::sync::Arc| { + let r_ref: &R = &*arc_root; + self.get(Root::from(r_ref)) + }, + move |mut arc_root: std::sync::Arc| { + // Get mutable reference only if we have exclusive ownership + std::sync::Arc::get_mut(&mut arc_root) + .and_then(|r_mut| self.get_mut(MutRoot::from(r_mut))) + }, + ) + } + + fn for_box<'a>( + &self, + ) -> Kp< + Box, + V, + Box, + Value, + Box, + MutValue, + impl Fn(Box) -> Option, + impl Fn(Box) -> Option, + > + where + R: 'a, + V: 'a, + Root: for<'b> From<&'b R>, + MutRoot: for<'b> From<&'b mut R>, + { + Kp::new( + move |r: Box| { + let r_ref: &R = r.as_ref(); + self.get(Root::from(r_ref)) + }, + move |mut r: Box| { + // Get mutable reference only if we have exclusive ownership + self.get_mut(MutRoot::from(r.as_mut())) + }, + ) + } +} + +pub trait HOFTrait: + KpTrait +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + // /// Map the value through a transformation function + // /// Returns a new keypath that transforms the value when accessed + // /// + // /// # Example + // /// ``` + // /// use rust_key_paths::{Kp, KpType}; + // /// struct User { name: String } + // /// let user = User { name: "Akash".to_string() }; + // /// let name_kp = KpType::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); + // /// let len_kp = name_kp.map(|name: &String| name.len()); + // /// assert_eq!(len_kp.get(&user), Some(5)); + // /// ``` + // fn map( + // &self, + // mapper: F, + // ) -> Kp< + // R, + // MappedValue, + // Root, + // MappedValue, + // MutRoot, + // MappedValue, + // impl Fn(Root) -> Option, + // impl Fn(MutRoot) -> Option, + // > + // where + // // Copy: Required because mapper is used in both getter and setter closures + // // 'static: Required because the returned Kp must own its closures + // F: Fn(&V) -> MappedValue + Copy + 'static, + // V: 'static, + // MappedValue: 'static, + // { + // Kp::new( + // move |root: Root| { + // &self.get(root).map(|value| { + // let v: &V = value.borrow(); + // mapper(v) + // }) + // }, + // move |root: MutRoot| { + // &self.get_mut(root).map(|value| { + // let v: &V = value.borrow(); + // mapper(v) + // }) + // }, + // ) + // } + + /// Map the value through a transformation function. + fn map( &self, mapper: F, ) -> Kp< @@ -948,25 +1401,23 @@ where MappedValue, MutRoot, MappedValue, - impl Fn(Root) -> Option, - impl Fn(MutRoot) -> Option, + impl Fn(Root) -> Option + '_, + impl Fn(MutRoot) -> Option + '_, > where - // Copy: Required because mapper is used in both getter and setter closures - // 'static: Required because the returned Kp must own its closures F: Fn(&V) -> MappedValue + Copy + 'static, V: 'static, MappedValue: 'static, { Kp::new( move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); mapper(v) }) }, move |root: MutRoot| { - (&self.set)(root).map(|value| { + self.get_mut(root).map(|value| { let v: &V = value.borrow(); mapper(v) }) @@ -974,19 +1425,8 @@ where ) } - /// Filter the value based on a predicate - /// Returns None if the predicate returns false, otherwise returns the value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { age: i32 } - /// let user = User { age: 30 }; - /// let age_kp = KpType::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); - /// let adult_kp = age_kp.filter(|age: &i32| *age >= 18); - /// assert_eq!(adult_kp.get(&user), Some(&30)); - /// ``` - pub fn filter( + /// Filter the value based on a predicate. + fn filter( &self, predicate: F, ) -> Kp< @@ -996,24 +1436,22 @@ where Value, MutRoot, MutValue, - impl Fn(Root) -> Option, - impl Fn(MutRoot) -> Option, + impl Fn(Root) -> Option + '_, + impl Fn(MutRoot) -> Option + '_, > where - // Copy: Required because predicate is used in both getter and setter closures - // 'static: Required because the returned Kp must own its closures F: Fn(&V) -> bool + Copy + 'static, V: 'static, { Kp::new( move |root: Root| { - (&self.get)(root).filter(|value| { + self.get(root).filter(|value| { let v: &V = value.borrow(); predicate(v) }) }, move |root: MutRoot| { - (&self.set)(root).filter(|value| { + self.get_mut(root).filter(|value| { let v: &V = value.borrow(); predicate(v) }) @@ -1021,19 +1459,8 @@ where ) } - /// Map and flatten - useful when mapper returns an Option - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { middle_name: Option } - /// let user = User { middle_name: Some("M.".to_string()) }; - /// let middle_kp = KpType::new(|u: &User| Some(&u.middle_name), |_| None); - /// let first_char_kp = middle_kp.filter_map(|opt: &Option| { - /// opt.as_ref().and_then(|s| s.chars().next()) - /// }); - /// ``` - pub fn filter_map( + /// Map and flatten when mapper returns an Option. + fn filter_map( &self, mapper: F, ) -> Kp< @@ -1043,25 +1470,23 @@ where MappedValue, MutRoot, MappedValue, - impl Fn(Root) -> Option, - impl Fn(MutRoot) -> Option, + impl Fn(Root) -> Option + '_, + impl Fn(MutRoot) -> Option + '_, > where - // Copy: Required because mapper is used in both getter and setter closures - // 'static: Required because the returned Kp must own its closures F: Fn(&V) -> Option + Copy + 'static, V: 'static, MappedValue: 'static, { Kp::new( move |root: Root| { - (&self.get)(root).and_then(|value| { + self.get(root).and_then(|value| { let v: &V = value.borrow(); mapper(v) }) }, move |root: MutRoot| { - (&self.set)(root).and_then(|value| { + self.get_mut(root).and_then(|value| { let v: &V = value.borrow(); mapper(v) }) @@ -1069,47 +1494,8 @@ where ) } - /// Flat map - maps to an iterator and flattens - /// Useful when the value is a collection and you want to iterate over it - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { tags: Vec<&'static str> } - /// let user = User { tags: vec!["rust", "web"] }; - /// let tags_kp = KpType::new(|u: &User| Some(&u.tags), |_| None); - /// // Use with a closure that returns an iterator - /// ``` - pub fn flat_map(&self, mapper: F) -> impl Fn(Root) -> Vec - where - // No Copy needed - mapper is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call - F: Fn(&V) -> I + 'static, - V: 'static, - I: IntoIterator, - Item: 'static, - { - move |root: Root| { - (&self.get)(root) - .map(|value| { - let v: &V = value.borrow(); - mapper(v).into_iter().collect() - }) - .unwrap_or_else(Vec::new) - } - } - - /// Apply a function for its side effects and return the value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { name: String } - /// let user = User { name: "Alice".to_string() }; - /// let name_kp = KpType::new(|u: &User| Some(&u.name), |_| None); - /// name_kp.inspect(|name| println!("Name: {}", name)).get(&user); - /// ``` - pub fn inspect( + /// Apply a function for its side effects and return the value. + fn inspect( &self, inspector: F, ) -> Kp< @@ -1119,57 +1505,58 @@ where Value, MutRoot, MutValue, - impl Fn(Root) -> Option, - impl Fn(MutRoot) -> Option, + impl Fn(Root) -> Option + '_, + impl Fn(MutRoot) -> Option + '_, > where - // Copy: Required because inspector is used in both getter and setter closures - // 'static: Required because the returned Kp must own its closures F: Fn(&V) + Copy + 'static, V: 'static, { Kp::new( move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); inspector(v); value }) }, move |root: MutRoot| { - (&self.set)(root).map(|value| { + self.get_mut(root).map(|value| { + let v: &V = value.borrow(); + inspector(v); + value + }) + }, + ) + } + + /// Flat map - maps to an iterator and flattens. + fn flat_map(&self, mapper: F) -> impl Fn(Root) -> Vec + '_ + where + F: Fn(&V) -> I + 'static, + V: 'static, + I: IntoIterator, + Item: 'static, + { + move |root: Root| { + self.get(root) + .map(|value| { let v: &V = value.borrow(); - inspector(v); - value + mapper(v).into_iter().collect() }) - }, - ) + .unwrap_or_else(Vec::new) + } } - /// Fold/reduce the value using an accumulator function - /// Useful when the value is a collection - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let sum = scores_kp.fold_value(0, |acc, scores| { - /// scores.iter().sum::() + acc - /// })(&user); - /// ``` - pub fn fold_value(&self, init: Acc, folder: F) -> impl Fn(Root) -> Acc + /// Fold/reduce the value using an accumulator function. + fn fold_value(&self, init: Acc, folder: F) -> impl Fn(Root) -> Acc + '_ where - // No Copy needed - folder is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(Acc, &V) -> Acc + 'static, V: 'static, - // Copy: Required for init since it's returned as default value Acc: Copy + 'static, { move |root: Root| { - (&self.get)(root) + self.get(root) .map(|value| { let v: &V = value.borrow(); folder(init, v) @@ -1178,26 +1565,14 @@ where } } - /// Check if any element satisfies a predicate (for collection values) - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let has_high = scores_kp.any(|scores| scores.iter().any(|&s| s > 90)); - /// assert!(has_high(&user)); - /// ``` - pub fn any(&self, predicate: F) -> impl Fn(Root) -> bool + /// Check if any element satisfies a predicate. + fn any(&self, predicate: F) -> impl Fn(Root) -> bool + '_ where - // No Copy needed - predicate is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> bool + 'static, V: 'static, { move |root: Root| { - (&self.get)(root) + self.get(root) .map(|value| { let v: &V = value.borrow(); predicate(v) @@ -1206,26 +1581,14 @@ where } } - /// Check if all elements satisfy a predicate (for collection values) - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let all_passing = scores_kp.all(|scores| scores.iter().all(|&s| s >= 70)); - /// assert!(all_passing(&user)); - /// ``` - pub fn all(&self, predicate: F) -> impl Fn(Root) -> bool + /// Check if all elements satisfy a predicate. + fn all(&self, predicate: F) -> impl Fn(Root) -> bool + '_ where - // No Copy needed - predicate is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> bool + 'static, V: 'static, { move |root: Root| { - (&self.get)(root) + self.get(root) .map(|value| { let v: &V = value.borrow(); predicate(v) @@ -1234,225 +1597,198 @@ where } } - /// Count elements in a collection value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { tags: Vec<&'static str> } - /// let user = User { tags: vec!["rust", "web", "backend"] }; - /// let tags_kp = KpType::new(|u: &User| Some(&u.tags), |_| None); - /// let count = tags_kp.count_items(|tags| tags.len()); - /// assert_eq!(count(&user), Some(3)); - /// ``` - pub fn count_items(&self, counter: F) -> impl Fn(Root) -> Option + /// Count elements in a collection value. + fn count_items(&self, counter: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - counter is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> usize + 'static, V: 'static, { move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); counter(v) }) } } - /// Find first element matching predicate in a collection value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78, 95] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let first_high = scores_kp.find_in(|scores| { - /// scores.iter().find(|&&s| s > 90).copied() - /// }); - /// assert_eq!(first_high(&user), Some(92)); - /// ``` - pub fn find_in(&self, finder: F) -> impl Fn(Root) -> Option + /// Find first element matching predicate in a collection value. + fn find_in(&self, finder: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - finder is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> Option + 'static, V: 'static, Item: 'static, { move |root: Root| { - (&self.get)(root).and_then(|value| { + self.get(root).and_then(|value| { let v: &V = value.borrow(); finder(v) }) } } - /// Take first N elements from a collection value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { tags: Vec<&'static str> } - /// let user = User { tags: vec!["a", "b", "c", "d"] }; - /// let tags_kp = KpType::new(|u: &User| Some(&u.tags), |_| None); - /// let first_two = tags_kp.take(2, |tags, n| tags.iter().take(n).cloned().collect::>()); - /// ``` - pub fn take(&self, n: usize, taker: F) -> impl Fn(Root) -> Option + /// Take first N elements from a collection value. + fn take(&self, n: usize, taker: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - taker is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V, usize) -> Output + 'static, V: 'static, Output: 'static, { move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); taker(v, n) }) } } - /// Skip first N elements from a collection value - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { tags: Vec<&'static str> } - /// let user = User { tags: vec!["a", "b", "c", "d"] }; - /// let tags_kp = KpType::new(|u: &User| Some(&u.tags), |_| None); - /// let after_two = tags_kp.skip(2, |tags, n| tags.iter().skip(n).cloned().collect::>()); - /// ``` - pub fn skip(&self, n: usize, skipper: F) -> impl Fn(Root) -> Option + /// Skip first N elements from a collection value. + fn skip(&self, n: usize, skipper: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - skipper is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V, usize) -> Output + 'static, V: 'static, Output: 'static, { move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); skipper(v, n) }) } } - /// Partition a collection value into two groups based on predicate - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 65, 95, 72] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let (passing, failing): (Vec, Vec) = scores_kp.partition_value(|scores| { - /// scores.iter().copied().partition(|&s| s >= 70) - /// })(&user).unwrap(); - /// ``` - pub fn partition_value(&self, partitioner: F) -> impl Fn(Root) -> Option + /// Partition a collection value into two groups based on predicate. + fn partition_value(&self, partitioner: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - partitioner is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> Output + 'static, V: 'static, Output: 'static, { move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); partitioner(v) }) } } - /// Get min value from a collection - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let min = scores_kp.min_value(|scores| scores.iter().min().copied()); - /// assert_eq!(min(&user), Some(78)); - /// ``` - pub fn min_value(&self, min_fn: F) -> impl Fn(Root) -> Option + /// Get min value from a collection. + fn min_value(&self, min_fn: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - min_fn is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> Option + 'static, V: 'static, Item: 'static, { move |root: Root| { - (&self.get)(root).and_then(|value| { + self.get(root).and_then(|value| { let v: &V = value.borrow(); min_fn(v) }) } } - /// Get max value from a collection - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let max = scores_kp.max_value(|scores| scores.iter().max().copied()); - /// assert_eq!(max(&user), Some(92)); - /// ``` - pub fn max_value(&self, max_fn: F) -> impl Fn(Root) -> Option + /// Get max value from a collection. + fn max_value(&self, max_fn: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - max_fn is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> Option + 'static, V: 'static, Item: 'static, { move |root: Root| { - (&self.get)(root).and_then(|value| { + self.get(root).and_then(|value| { let v: &V = value.borrow(); max_fn(v) }) } } - /// Sum numeric values in a collection - /// - /// # Example - /// ``` - /// use rust_key_paths::{Kp, KpType}; - /// struct User { scores: Vec } - /// let user = User { scores: vec![85, 92, 78] }; - /// let scores_kp = KpType::new(|u: &User| Some(&u.scores), |_| None); - /// let sum = scores_kp.sum_value(|scores: &Vec| scores.iter().sum()); - /// assert_eq!(sum(&user), Some(255)); - /// ``` - pub fn sum_value(&self, sum_fn: F) -> impl Fn(Root) -> Option + /// Sum numeric values in a collection. + fn sum_value(&self, sum_fn: F) -> impl Fn(Root) -> Option + '_ where - // No Copy needed - sum_fn is only captured once by the returned closure - // 'static: Required so the returned function can outlive the call F: Fn(&V) -> Sum + 'static, V: 'static, Sum: 'static, { move |root: Root| { - (&self.get)(root).map(|value| { + self.get(root).map(|value| { let v: &V = value.borrow(); sum_fn(v) }) } } - /// Chain this keypath with another to create a composition - /// Alias for `then` with a more descriptive name - pub fn chain( + // /// Zip two keypaths on the same root; get_mut returns None. + // fn zip( + // self, + // other: Kp, + // ) -> Kp< + // R, + // (Value, Value2), + // Root, + // (Value, Value2), + // MutRoot, + // (Value, Value2), + // impl Fn(Root) -> Option<(Value, Value2)>, + // impl Fn(MutRoot) -> Option<(Value, Value2)>, + // > + // where + // Root: Copy, + // Value2: std::borrow::Borrow, + // MutValue2: std::borrow::BorrowMut, + // G2: Fn(Root) -> Option, + // S2: Fn(MutRoot) -> Option, + // V2: 'static { + // todo!() + // } + + // /// Zip two keypaths and transform the pair with a function; get_mut returns None. + // fn zip_with( + // &self, + // other: Kp, + // transform: F, + // ) -> Kp< + // R, + // Z, + // Root, + // Z, + // MutRoot, + // Z, + // impl Fn(Root) -> Option, + // impl Fn(MutRoot) -> Option, + // > + // where + // Root: Copy, + // Value2: std::borrow::Borrow, + // MutValue2: std::borrow::BorrowMut, + // G2: Fn(Root) -> Option, + // S2: Fn(MutRoot) -> Option, + // F: Fn(Value, Value2) -> Z + Copy + 'static, + // V2: 'static, + // Z: 'static { + // todo!() + // } +} + +impl KpTrait + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + #[inline] + fn get(&self, root: Root) -> Option { + (self.get)(root) + } + + #[inline] + fn get_mut(&self, root: MutRoot) -> Option { + (self.set)(root) + } + + fn then( self, next: Kp, ) -> Kp< @@ -1471,72 +1807,143 @@ where G2: Fn(Value) -> Option, S2: Fn(MutValue) -> Option, V: 'static, - { - self.then(next) - } - - pub fn for_arc<'b>( - &self, - ) -> Kp< - std::sync::Arc, - V, - std::sync::Arc, - Value, - std::sync::Arc, - MutValue, - impl Fn(std::sync::Arc) -> Option, - impl Fn(std::sync::Arc) -> Option, - > - where - R: 'b, - V: 'b, - Root: for<'a> From<&'a R>, - MutRoot: for<'a> From<&'a mut R>, { Kp::new( - move |arc_root: std::sync::Arc| { - let r_ref: &R = &*arc_root; - (&self.get)(Root::from(r_ref)) - }, - move |mut arc_root: std::sync::Arc| { - // Get mutable reference only if we have exclusive ownership - std::sync::Arc::get_mut(&mut arc_root) - .and_then(|r_mut| (&self.set)(MutRoot::from(r_mut))) - }, + move |root: Root| (self.get)(root).and_then(|value| (next.get)(value)), + move |root: MutRoot| (self.set)(root).and_then(|value| (next.set)(value)), ) } +} - pub fn for_box<'a>( - &self, +impl + CoercionTrait + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ +} + +impl + HOFTrait + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ +} +/// AKp (AnyKeyPath) - Hides both Root and Value types +/// Most flexible keypath type for heterogeneous collections +/// Uses dynamic dispatch and type checking at runtime +/// +/// # Mutation: get vs get_mut (setter path) +/// +/// - **[get](Kp::get)** uses the `get` closure (getter): `Fn(Root) -> Option` +/// - **[get_mut](Kp::get_mut)** uses the `set` closure (setter): `Fn(MutRoot) -> Option` +/// +/// When mutating through a Kp, the **setter path** is used—`get_mut` invokes the `set` closure, +/// not the `get` closure. The getter is for read-only access only. +#[derive(Clone)] +pub struct Kp +where + Root: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + /// Getter closure: used by [Kp::get] for read-only access. + pub(crate) get: G, + /// Setter closure: used by [Kp::get_mut] for mutation. + pub(crate) set: S, + _p: std::marker::PhantomData<(R, V, Root, Value, MutRoot, MutValue)>, +} + +// Kp is a functional component (get/set) with no owned data; Send/Sync follow from G and S. +unsafe impl Send + for Kp +where + Root: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option + Send, + S: Fn(MutRoot) -> Option + Send, +{ +} +unsafe impl Sync + for Kp +where + Root: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option + Sync, + S: Fn(MutRoot) -> Option + Sync, +{ +} + +impl Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + pub fn new(get: G, set: S) -> Self { + Self { + get: get, + set: set, + _p: std::marker::PhantomData, + } + } + + #[inline] + pub fn get(&self, root: Root) -> Option { + (self.get)(root) + } + + #[inline] + pub fn get_mut(&self, root: MutRoot) -> Option { + (self.set)(root) + } + + #[inline] + pub fn then( + self, + next: Kp, ) -> Kp< - Box, - V, - Box, - Value, - Box, - MutValue, - impl Fn(Box) -> Option, - impl Fn(Box) -> Option, + R, + SV, + Root, + SubValue, + MutRoot, + MutSubValue, + impl Fn(Root) -> Option, + impl Fn(MutRoot) -> Option, > where - R: 'a, - V: 'a, - Root: for<'b> From<&'b R>, - MutRoot: for<'b> From<&'b mut R>, + SubValue: std::borrow::Borrow, + MutSubValue: std::borrow::BorrowMut, + G2: Fn(Value) -> Option, + S2: Fn(MutValue) -> Option, + V: 'static, { Kp::new( - move |r: Box| { - let r_ref: &R = r.as_ref(); - (&self.get)(Root::from(r_ref)) - }, - move |mut r: Box| { - // Get mutable reference only if we have exclusive ownership - (self.set)(MutRoot::from(r.as_mut())) - }, + move |root: Root| (self.get)(root).and_then(|value| (next.get)(value)), + move |root: MutRoot| (self.set)(root).and_then(|value| (next.set)(value)), ) } -} +} /// Zip two keypaths together to create a tuple /// Works only with KpType (reference-based keypaths) /// @@ -1544,11 +1951,11 @@ where /// ``` /// use rust_key_paths::{KpType, zip_kps}; /// struct User { name: String, age: i32 } -/// let user = User { name: "Alice".to_string(), age: 30 }; +/// let user = User { name: "Akash".to_string(), age: 30 }; /// let name_kp = KpType::new(|u: &User| Some(&u.name), |_| None); /// let age_kp = KpType::new(|u: &User| Some(&u.age), |_| None); /// let zipped_fn = zip_kps(&name_kp, &age_kp); -/// assert_eq!(zipped_fn(&user), Some((&"Alice".to_string(), &30))); +/// assert_eq!(zipped_fn(&user), Some((&"Akash".to_string(), &30))); /// ``` pub fn zip_kps<'a, RootType, Value1, Value2>( kp1: &'a KpType<'a, RootType, Value1>, @@ -1983,6 +2390,15 @@ mod tests { use super::*; use std::collections::HashMap; + fn kp_adaptable(kp: T) + where + T: KpTrait, + { + // kp.get + // .get_mut + } + fn test_kp_trait() {} + #[derive(Debug)] struct TestKP { a: String, @@ -2164,7 +2580,10 @@ mod tests { let kp = TestKP::identity(); let kp_a = TestKP::a(); // TestKP::a().for_arc(); - let wres = TestKP::f().then(TestKP2::a()).get_mut(&mut instance).unwrap(); + let wres = TestKP::f() + .then(TestKP2::a()) + .get_mut(&mut instance) + .unwrap(); *wres = String::from("a3 changed successfully"); let res = TestKP::f().then(TestKP2::a()).get(&instance); println!("{:?}", res); @@ -2177,6 +2596,49 @@ mod tests { println!("{:?}", new_kp_from_hashmap.get(&instance)); } + // #[test] + // fn test_get_or_and_get_or_else() { + // struct User { + // name: String, + // } + // impl User { + // fn name() -> KpType<'static, User, String> { + // KpType::new( + // |u: &User| Some(&u.name), + // |u: &mut User| Some(&mut u.name), + // ) + // } + // } + // let user = User { + // name: "Akash".to_string(), + // }; + // let default_ref: String = "default".to_string(); + // // get_or with kp form + // let r = get_or!(User::name(), &user, &default_ref); + // assert_eq!(*r, "Akash"); + // // get_or_else with kp form (returns owned) + // let owned = get_or_else!(User::name(), &user, || "fallback".to_string()); + // assert_eq!(owned, "Akash"); + + // // When path returns None, fallback is used + // struct WithOption { + // opt: Option, + // } + // impl WithOption { + // fn opt() -> KpType<'static, WithOption, String> { + // KpType::new( + // |w: &WithOption| w.opt.as_ref(), + // |_w: &mut WithOption| None, + // ) + // } + // } + // let with_none = WithOption { opt: None }; + // let r = get_or!(WithOption::opt(), &with_none, &default_ref); + // assert_eq!(*r, "default"); + // let owned = get_or_else!(&with_none => (WithOption.opt), || "computed".to_string()); + // assert_eq!(owned, "computed"); + // } + // #[test] // fn test_lock() { // let lock_kp = LockKp::new(A::b(), kp_arc_mutex::(), B::c()); @@ -2402,7 +2864,7 @@ mod tests { } let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -2415,7 +2877,7 @@ mod tests { let age_pkp = PKp::new(age_kp); // Test get_as with correct type - assert_eq!(name_pkp.get_as::(&user), Some(&"Alice".to_string())); + assert_eq!(name_pkp.get_as::(&user), Some(&"Akash".to_string())); assert_eq!(age_pkp.get_as::(&user), Some(&30)); // Test get_as with wrong type returns None @@ -2673,7 +3135,7 @@ mod tests { } let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -2703,7 +3165,7 @@ mod tests { } let adult = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -2807,7 +3269,7 @@ mod tests { } let adult = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -2911,7 +3373,7 @@ mod tests { } let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), }; // Simple test - just verify that inspect returns the correct value @@ -2922,7 +3384,7 @@ mod tests { // We can't easily test side effects with Copy constraint, // so we'll just test that inspect passes through the value let result = name_kp.get(&user); - assert_eq!(result, Some(&"Alice".to_string())); + assert_eq!(result, Some(&"Akash".to_string())); // The inspect method works, it just requires Copy closures // which limits its usefulness for complex side effects @@ -3176,8 +3638,8 @@ mod tests { ); // Chain all together - store intermediate result - let profile_settings = profile_kp.chain(settings_kp); - let theme_path = profile_settings.chain(theme_kp); + let profile_settings = profile_kp.then(settings_kp); + let theme_path = profile_settings.then(theme_kp); assert_eq!(theme_path.get(&user), Some(&"dark".to_string())); } @@ -3190,7 +3652,7 @@ mod tests { } let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, }; @@ -3200,7 +3662,7 @@ mod tests { let zipped_fn = zip_kps(&name_kp, &age_kp); let result = zipped_fn(&user); - assert_eq!(result, Some((&"Alice".to_string(), &30))); + assert_eq!(result, Some((&"Akash".to_string(), &30))); } #[test] @@ -3799,7 +4261,7 @@ mod tests { } let user = User { - name: "Alice".to_string(), + name: "Akash".to_string(), age: 30, score: 95.5, active: true, @@ -3831,7 +4293,7 @@ mod tests { assert_eq!(string_kps.len(), 1); assert_eq!( string_kps[0].get_as::(&user), - Some(&"Alice".to_string()) + Some(&"Akash".to_string()) ); // Filter for i32 types @@ -4830,8 +5292,10 @@ mod tests { Kp::new(|r: &Root| Some(&r.guard), |r: &mut Root| Some(&mut r.guard)); let lock_kp = { - let prev: KpType>, Arc>> = - Kp::new(|g: &Arc>| Some(g), |g: &mut Arc>| Some(g)); + let prev: KpType>, Arc>> = Kp::new( + |g: &Arc>| Some(g), + |g: &mut Arc>| Some(g), + ); let next: KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); crate::lock::LockKp::new(prev, crate::lock::ArcMutexAccess::new(), next) @@ -4874,12 +5338,16 @@ mod tests { }))), }; - let kp_msg: KpType>> = - Kp::new(|r: &RootWithEnum| Some(&r.msg), |r: &mut RootWithEnum| Some(&mut r.msg)); + let kp_msg: KpType>> = Kp::new( + |r: &RootWithEnum| Some(&r.msg), + |r: &mut RootWithEnum| Some(&mut r.msg), + ); let lock_kp_msg = { - let prev: KpType>, Arc>> = - Kp::new(|m: &Arc>| Some(m), |m: &mut Arc>| Some(m)); + let prev: KpType>, Arc>> = Kp::new( + |m: &Arc>| Some(m), + |m: &mut Arc>| Some(m), + ); let next: KpType = Kp::new(|m: &Message| Some(m), |m: &mut Message| Some(m)); crate::lock::LockKp::new(prev, crate::lock::ArcMutexAccess::new(), next) @@ -4897,8 +5365,8 @@ mod tests { #[cfg(all(feature = "tokio", feature = "parking_lot"))] #[tokio::test] async fn test_kp_then_async_deep_chain() { - use std::sync::Arc; use crate::async_lock::{AsyncLockKp, TokioMutexAccess}; + use std::sync::Arc; #[derive(Clone)] struct Root { @@ -4940,9 +5408,9 @@ mod tests { #[cfg(all(feature = "tokio", feature = "parking_lot"))] #[tokio::test] async fn test_deep_nested_chain_kp_lock_async_lock_kp() { - use std::sync::{Arc, Mutex}; use crate::async_lock::{AsyncLockKp, TokioMutexAccess}; - use crate::lock::{LockKp, ArcMutexAccess}; + use crate::lock::{ArcMutexAccess, LockKp}; + use std::sync::{Arc, Mutex}; // Root -> Arc> #[derive(Clone)] @@ -4976,13 +5444,17 @@ mod tests { // LockKp from Root -> Level1 let identity_l1: KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); - let kp_sync: KpType>> = - Kp::new(|r: &Root| Some(&r.sync_mutex), |r: &mut Root| Some(&mut r.sync_mutex)); + let kp_sync: KpType>> = Kp::new( + |r: &Root| Some(&r.sync_mutex), + |r: &mut Root| Some(&mut r.sync_mutex), + ); let lock_root_to_l1 = LockKp::new(kp_sync, ArcMutexAccess::new(), identity_l1); // Kp: Level1 -> Level2 - let kp_l1_inner: KpType = - Kp::new(|l: &Level1| Some(&l.inner), |l: &mut Level1| Some(&mut l.inner)); + let kp_l1_inner: KpType = Kp::new( + |l: &Level1| Some(&l.inner), + |l: &mut Level1| Some(&mut l.inner), + ); // Kp: Level2 -> Arc> let kp_l2_tokio: KpType>> = Kp::new( @@ -5000,8 +5472,10 @@ mod tests { }; // Kp: Level3 -> i32 - let kp_l3_leaf: KpType = - Kp::new(|l: &Level3| Some(&l.leaf), |l: &mut Level3| Some(&mut l.leaf)); + let kp_l3_leaf: KpType = Kp::new( + |l: &Level3| Some(&l.leaf), + |l: &mut Level3| Some(&mut l.leaf), + ); // Build chain: LockKp(Root->L1) . then(L1->L2) . then(L2->tokio) . then_async(tokio->L3) . then(L3->leaf) let step1 = lock_root_to_l1.then(kp_l1_inner); diff --git a/src/lock.rs b/src/lock.rs index 38e4151..45b5fbe 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -294,6 +294,40 @@ where }) } + /// Like [get](LockKp::get), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the getter. + #[inline] + pub fn get_optional(&self, root: Option) -> Option + where + V: Clone, + { + root.and_then(|r| self.get(r)) + } + + /// Like [get_mut](LockKp::get_mut), but takes an optional root: returns `None` if `root` is `None`, otherwise the result of the setter. + #[inline] + pub fn get_mut_optional(&self, root: Option) -> Option { + root.and_then(|r| self.get_mut(r)) + } + + /// Returns the value if the keypath succeeds (root is `Some` and get returns `Some`), otherwise calls `f` and returns its result. + #[inline] + pub fn get_or_else(&self, root: Option, f: F) -> Value + where + V: Clone, + F: FnOnce() -> Value, + { + self.get_optional(root).unwrap_or_else(f) + } + + /// Returns the mutable value if the keypath succeeds (root is `Some` and get_mut returns `Some`), otherwise calls `f` and returns its result. + #[inline] + pub fn get_mut_or_else(&self, root: Option, f: F) -> MutValue + where + F: FnOnce() -> MutValue, + { + self.get_mut_optional(root).unwrap_or_else(f) + } + /// Set the value through the lock using an updater function /// /// # NO CLONING Required! @@ -351,8 +385,56 @@ where G1, S1, L, - impl Fn(MidValue) -> Option + use, - impl Fn(MutMid) -> Option + use, + impl Fn(MidValue) -> Option + + use< + G1, + G2, + G3, + L, + Lock, + LockValue, + Mid, + MidValue, + MutLock, + MutMid, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S3, + Value, + Value2, + V, + V2, + >, + impl Fn(MutMid) -> Option + + use< + G1, + G2, + G3, + L, + Lock, + LockValue, + Mid, + MidValue, + MutLock, + MutMid, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S3, + Value, + Value2, + V, + V2, + >, > where V: 'static, @@ -454,8 +536,74 @@ where G1, S1, L, - impl Fn(MidValue) -> Option + use, - impl Fn(MutMid) -> Option + use, + impl Fn(MidValue) -> Option + + use< + G1, + G2, + G2_1, + G2_2, + L, + L2, + Lock, + Lock2, + LockValue, + LockValue2, + Mid, + Mid2, + MidValue, + MidValue2, + MutLock, + MutLock2, + MutMid, + MutMid2, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S2_1, + S2_2, + Value, + Value2, + V, + V2, + >, + impl Fn(MutMid) -> Option + + use< + G1, + G2, + G2_1, + G2_2, + L, + L2, + Lock, + Lock2, + LockValue, + LockValue2, + Mid, + Mid2, + MidValue, + MidValue2, + MutLock, + MutLock2, + MutMid, + MutMid2, + MutRoot, + MutValue, + MutValue2, + R, + Root, + S1, + S2, + S2_1, + S2_2, + Value, + Value2, + V, + V2, + >, > where V: 'static + Clone, @@ -547,7 +695,8 @@ where AsyncKp: crate::async_lock::AsyncKeyPathLike, AsyncKp::Value: crate::KeyPathValueTarget + std::borrow::Borrow<::Target>, - AsyncKp::MutValue: std::borrow::BorrowMut<::Target>, + AsyncKp::MutValue: + std::borrow::BorrowMut<::Target>, ::Target: 'static, { crate::async_lock::KpThenAsyncKeyPath { @@ -564,10 +713,12 @@ where /// Keypath that chains a [crate::Kp] with a [LockKp]. Use [crate::Kp::then_lock] to create. #[derive(Clone)] -pub struct KpThenLockKp { +pub struct KpThenLockKp +{ pub(crate) first: First, pub(crate) second: Second, - pub(crate) _p: std::marker::PhantomData<(R, V, V2, Root, Value, Value2, MutRoot, MutValue, MutValue2)>, + pub(crate) _p: + std::marker::PhantomData<(R, V, V2, Root, Value, Value2, MutRoot, MutValue, MutValue2)>, } impl @@ -591,6 +742,40 @@ where let mut_v = self.first.sync_get_mut(root)?; self.second.sync_get_mut(mut_v) } + + /// Like [get](KpThenLockKp::get), but takes an optional root. + #[inline] + pub fn get_optional(&self, root: Option) -> Option + where + Value2: Clone, + { + root.and_then(|r| self.get(r)) + } + + /// Like [get_mut](KpThenLockKp::get_mut), but takes an optional root. + #[inline] + pub fn get_mut_optional(&self, root: Option) -> Option { + root.and_then(|r| self.get_mut(r)) + } + + /// Returns the value if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + pub fn get_or_else(&self, root: Option, f: F) -> Value2 + where + Value2: Clone, + F: FnOnce() -> Value2, + { + self.get_optional(root).unwrap_or_else(f) + } + + /// Returns the mutable value if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + pub fn get_mut_or_else(&self, root: Option, f: F) -> MutValue2 + where + F: FnOnce() -> MutValue2, + { + self.get_mut_optional(root).unwrap_or_else(f) + } } // ============================================================================ @@ -1345,6 +1530,27 @@ pub type LockKpArcMutexFor = LockKp< for<'b> fn(&'b mut Inner) -> Option<&'b mut Inner>, >; +/// Type alias for LockKp over Arc>>; value is T (extract from Option). +pub type LockKpArcMutexOptionFor = LockKp< + Root, + Lock, + Option, + Inner, + &'static Root, + &'static Lock, + &'static Option, + &'static Inner, + &'static mut Root, + &'static mut Lock, + &'static mut Option, + &'static mut Inner, + for<'b> fn(&'b Root) -> Option<&'b Lock>, + for<'b> fn(&'b mut Root) -> Option<&'b mut Lock>, + ArcMutexAccess>, + for<'b> fn(&'b Option) -> Option<&'b Inner>, + for<'b> fn(&'b mut Option) -> Option<&'b mut Inner>, +>; + /// Type alias for LockKp over Arc>. Use with derive macro's `_lock()` methods. pub type LockKpArcRwLockFor = LockKp< Root, @@ -1366,6 +1572,27 @@ pub type LockKpArcRwLockFor = LockKp< for<'b> fn(&'b mut Inner) -> Option<&'b mut Inner>, >; +/// Type alias for LockKp over Arc>>; value is T (extract from Option). +pub type LockKpArcRwLockOptionFor = LockKp< + Root, + Lock, + Option, + Inner, + &'static Root, + &'static Lock, + &'static Option, + &'static Inner, + &'static mut Root, + &'static mut Lock, + &'static mut Option, + &'static mut Inner, + for<'b> fn(&'b Root) -> Option<&'b Lock>, + for<'b> fn(&'b mut Root) -> Option<&'b mut Lock>, + ArcRwLockAccess>, + for<'b> fn(&'b Option) -> Option<&'b Inner>, + for<'b> fn(&'b mut Option) -> Option<&'b mut Inner>, +>; + #[cfg(feature = "parking_lot")] /// Type alias for LockKp over Arc>. Use with derive macro's `_lock()` methods. pub type LockKpParkingLotMutexFor = LockKp< @@ -1388,6 +1615,28 @@ pub type LockKpParkingLotMutexFor = LockKp< for<'b> fn(&'b mut Inner) -> Option<&'b mut Inner>, >; +#[cfg(feature = "parking_lot")] +/// Type alias for LockKp over Arc>>; value is T (extract from Option). +pub type LockKpParkingLotMutexOptionFor = LockKp< + Root, + Lock, + Option, + Inner, + &'static Root, + &'static Lock, + &'static Option, + &'static Inner, + &'static mut Root, + &'static mut Lock, + &'static mut Option, + &'static mut Inner, + for<'b> fn(&'b Root) -> Option<&'b Lock>, + for<'b> fn(&'b mut Root) -> Option<&'b mut Lock>, + ParkingLotMutexAccess>, + for<'b> fn(&'b Option) -> Option<&'b Inner>, + for<'b> fn(&'b mut Option) -> Option<&'b mut Inner>, +>; + #[cfg(feature = "parking_lot")] /// Type alias for LockKp over Arc>. Use with derive macro's `_lock()` methods. pub type LockKpParkingLotRwLockFor = LockKp< @@ -1410,6 +1659,28 @@ pub type LockKpParkingLotRwLockFor = LockKp< for<'b> fn(&'b mut Inner) -> Option<&'b mut Inner>, >; +#[cfg(feature = "parking_lot")] +/// Type alias for LockKp over Arc>>; value is T (extract from Option). +pub type LockKpParkingLotRwLockOptionFor = LockKp< + Root, + Lock, + Option, + Inner, + &'static Root, + &'static Lock, + &'static Option, + &'static Inner, + &'static mut Root, + &'static mut Lock, + &'static mut Option, + &'static mut Inner, + for<'b> fn(&'b Root) -> Option<&'b Lock>, + for<'b> fn(&'b mut Root) -> Option<&'b mut Lock>, + ParkingLotRwLockAccess>, + for<'b> fn(&'b Option) -> Option<&'b Inner>, + for<'b> fn(&'b mut Option) -> Option<&'b mut Inner>, +>; + /// Type alias for common LockKp usage with Arc> pub type LockKpType<'a, R, Mid, V> = LockKp< R, @@ -1475,6 +1746,85 @@ mod tests { // Note: Direct comparison may not work due to lifetime issues in this simple test } + #[test] + fn test_lock_kp_get_optional_or_else() { + #[derive(Debug, Clone)] + struct Root { + locked_data: Arc>, + } + + #[derive(Debug, Clone)] + struct Inner { + value: i32, + } + + let mut root = Root { + locked_data: Arc::new(Mutex::new(Inner { value: 42 })), + }; + + let prev_kp: KpType>> = Kp::new( + |r: &Root| Some(&r.locked_data), + |r: &mut Root| Some(&mut r.locked_data), + ); + let next_kp: KpType = Kp::new( + |i: &Inner| Some(&i.value), + |i: &mut Inner| Some(&mut i.value), + ); + let lock_kp = LockKp::new(prev_kp, ArcMutexAccess::new(), next_kp); + + // get_optional + assert!(lock_kp.get_optional(None).is_none()); + assert_eq!(lock_kp.get_optional(Some(&root)), Some(&42)); + + // get_mut_optional + assert!(lock_kp.get_mut_optional(None).is_none()); + if let Some(m) = lock_kp.get_mut_optional(Some(&mut root)) { + *m = 99; + } + assert_eq!(lock_kp.get(&root), Some(&99)); + + // get_or_else + static DEFAULT: i32 = -1; + let fallback = || &DEFAULT; + assert_eq!(*lock_kp.get_or_else(None, fallback), -1); + assert_eq!(*lock_kp.get_or_else(Some(&root), fallback), 99); + + // get_mut_or_else: with Some we get the value; with None the fallback would be used (we only test Some here to avoid static mut) + let m_some = lock_kp.get_mut_or_else(Some(&mut root), || panic!("should not use fallback")); + *m_some = 100; + assert_eq!(lock_kp.get(&root), Some(&100)); + } + + #[test] + fn test_kp_then_lock_kp_get_optional_or_else() { + #[derive(Debug, Clone)] + struct Root { + data: Arc>, + } + + #[derive(Debug, Clone)] + struct Mid { + value: i32, + } + + let _root = Root { + data: Arc::new(Mutex::new(Mid { value: 10 })), + }; + + let prev: KpType>> = + Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); + let next: KpType = + Kp::new(|m: &Mid| Some(&m.value), |m: &mut Mid| Some(&mut m.value)); + let lock_kp = LockKp::new(prev, ArcMutexAccess::new(), next); + + assert!(lock_kp.get_optional(None).is_none()); + assert_eq!(lock_kp.get_optional(Some(&_root)), Some(&10)); + + static DEF: i32 = -1; + assert_eq!(*lock_kp.get_or_else(None, || &DEF), -1); + assert_eq!(*lock_kp.get_or_else(Some(&_root), || &DEF), 10); + } + #[test] fn test_lock_kp_structure() { // This test verifies that the structure has the three required fields @@ -3047,4 +3397,4 @@ mod tests { let value = lock_kp.get(&root); assert_eq!(value.as_ref().map(|s| s.as_str()), Some("world")); } -} \ No newline at end of file +} diff --git a/src/pin.rs b/src/pin.rs index 1f15a11..94ac029 100644 --- a/src/pin.rs +++ b/src/pin.rs @@ -109,4 +109,40 @@ where let s: &mut S = mut_value.borrow_mut(); self.second.get_await(Pin::new(s)).await } + + /// Like [get](KpThenPinFuture::get), but takes an optional root. + #[inline] + pub async fn get_optional(&self, root: Option) -> Option { + match root { + Some(r) => self.get(r).await, + None => None, + } + } + + /// Like [get_mut](KpThenPinFuture::get_mut), but takes an optional root. + #[inline] + pub async fn get_mut_optional(&self, root: Option) -> Option { + match root { + Some(r) => self.get_mut(r).await, + None => None, + } + } + + /// Returns the value if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + pub async fn get_or_else(&self, root: Option, f: F) -> Output + where + F: FnOnce() -> Output, + { + self.get_optional(root).await.unwrap_or_else(f) + } + + /// Returns the value (from get_mut) if the keypath succeeds, otherwise calls `f` and returns its result. + #[inline] + pub async fn get_mut_or_else(&self, root: Option, f: F) -> Output + where + F: FnOnce() -> Output, + { + self.get_mut_optional(root).await.unwrap_or_else(f) + } } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..221f09b --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,5 @@ +// src/prelude.rs +pub use crate::CoercionTrait; +pub use crate::Kp; +pub use crate::KpTrait; +pub use crate::KpType; diff --git a/tests/integration_async_lock.rs b/tests/integration_async_lock.rs index ed3b328..47d9420 100644 --- a/tests/integration_async_lock.rs +++ b/tests/integration_async_lock.rs @@ -52,24 +52,30 @@ async fn integration_async_lock_then_lock_then_chain() { }; let parking_kp = { - let prev: KpType<'_, Level1, Arc>> = - Kp::new(|l1: &Level1| Some(&l1.parking), |l1: &mut Level1| Some(&mut l1.parking)); + let prev: KpType<'_, Level1, Arc>> = Kp::new( + |l1: &Level1| Some(&l1.parking), + |l1: &mut Level1| Some(&mut l1.parking), + ); let next: KpType<'_, Level2, Level2> = Kp::new(|l2: &Level2| Some(l2), |l2: &mut Level2| Some(l2)); LockKp::new(prev, ParkingLotMutexAccess::new(), next) }; let async_kp = { - let prev: KpType<'_, Level2, Arc>> = - Kp::new(|l2: &Level2| Some(&l2.rwlock), |l2: &mut Level2| Some(&mut l2.rwlock)); + let prev: KpType<'_, Level2, Arc>> = Kp::new( + |l2: &Level2| Some(&l2.rwlock), + |l2: &mut Level2| Some(&mut l2.rwlock), + ); let next: KpType<'_, Level3, Level3> = Kp::new(|l3: &Level3| Some(l3), |l3: &mut Level3| Some(l3)); AsyncLockKp::new(prev, TokioRwLockAccess::new(), next) }; let std_lock_kp = { - let prev: KpType<'_, Level3, std::sync::RwLock> = - Kp::new(|l3: &Level3| Some(&l3.value), |l3: &mut Level3| Some(&mut l3.value)); + let prev: KpType<'_, Level3, std::sync::RwLock> = Kp::new( + |l3: &Level3| Some(&l3.value), + |l3: &mut Level3| Some(&mut l3.value), + ); let next: KpType<'_, i32, i32> = Kp::new(|v: &i32| Some(v), |v: &mut i32| Some(v)); LockKp::new(prev, StdRwLockAccess::new(), next) }; diff --git a/tests/integration_nine_nesting.rs b/tests/integration_nine_nesting.rs index bba94da..fbae049 100644 --- a/tests/integration_nine_nesting.rs +++ b/tests/integration_nine_nesting.rs @@ -8,7 +8,7 @@ #![cfg(all(feature = "tokio", feature = "parking_lot"))] use rust_key_paths::async_lock::{AsyncLockKp, TokioMutexAccess}; -use rust_key_paths::lock::{LockKp, ArcMutexAccess, ParkingLotMutexAccess}; +use rust_key_paths::lock::{ArcMutexAccess, LockKp, ParkingLotMutexAccess}; use rust_key_paths::{Kp, KpType}; use std::sync::{Arc, Mutex}; @@ -156,8 +156,10 @@ async fn all_nine_nesting_combinations() { let kp_rm: KpType>> = Kp::new(|r: &Root2| Some(&r.m), |r: &mut Root2| Some(&mut r.m)); let lock_bx = { - let prev: KpType>, Arc>> = - Kp::new(|m: &Arc>| Some(m), |m: &mut Arc>| Some(m)); + let prev: KpType>, Arc>> = Kp::new( + |m: &Arc>| Some(m), + |m: &mut Arc>| Some(m), + ); let next: KpType = Kp::new(|b: &B2| Some(&b.x), |b: &mut B2| Some(&mut b.x)); LockKp::new(prev, ArcMutexAccess::new(), next) }; @@ -181,9 +183,7 @@ async fn all_nine_nesting_combinations() { // 4. LockKp → Kp let root4 = Root4 { - m: Arc::new(Mutex::new(D4 { - e: E4 { z: 4 }, - })), + m: Arc::new(Mutex::new(D4 { e: E4 { z: 4 } })), }; let lock_rd = { let prev: KpType>> = diff --git a/tests/integration_pin_future.rs b/tests/integration_pin_future.rs index 8ae3401..4923243 100644 --- a/tests/integration_pin_future.rs +++ b/tests/integration_pin_future.rs @@ -34,8 +34,10 @@ async fn test_then_pin_future_identity() { }; // Identity Kp to the struct, then_pin_future awaits the #[pin] Future field - let identity_kp: KpType = - Kp::new(|x: &WithPinnedBoxFuture| Some(x), |x: &mut WithPinnedBoxFuture| Some(x)); + let identity_kp: KpType = Kp::new( + |x: &WithPinnedBoxFuture| Some(x), + |x: &mut WithPinnedBoxFuture| Some(x), + ); let kp = identity_kp.then_pin_future(WithPinnedBoxFuture::fut_pin_future_kp()); let result = kp.get_mut(&mut data).await; @@ -53,9 +55,47 @@ async fn test_then_pin_future_go_deeper() { }; // Navigate to inner field (sync), then await its #[pin] Future - let kp = Wrapper::inner() - .then_pin_future(WithPinnedBoxFuture::fut_pin_future_kp()); + let kp = Wrapper::inner().then_pin_future(WithPinnedBoxFuture::fut_pin_future_kp()); let result = kp.get_mut(&mut data).await; assert_eq!(result, Some(99)); } + +#[tokio::test] +async fn test_then_pin_future_get_optional_or_else() { + use std::future::ready; + + let mut data = WithPinnedBoxFuture { + fut: Box::pin(ready(21)), + }; + + let identity_kp: KpType = Kp::new( + |x: &WithPinnedBoxFuture| Some(x), + |x: &mut WithPinnedBoxFuture| Some(x), + ); + let kp = identity_kp.then_pin_future(WithPinnedBoxFuture::fut_pin_future_kp()); + + // get_optional + assert!( + kp.get_optional(None::<&WithPinnedBoxFuture>) + .await + .is_none() + ); + assert_eq!(kp.get_optional(Some(&data)).await, None); // get returns None for pin future + assert_eq!( + kp.get_mut_optional(None::<&mut WithPinnedBoxFuture>).await, + None + ); + assert_eq!(kp.get_mut_optional(Some(&mut data)).await, Some(21)); + + // get_or_else / get_mut_or_else + assert_eq!(kp.get_or_else(None, || 0).await, 0); + assert_eq!(kp.get_or_else(Some(&data), || 0).await, 0); // get is None so fallback + assert_eq!(kp.get_mut_or_else(None, || 100).await, 100); + + // get_mut_or_else with Some uses a fresh future (previous get_mut consumed the first) + let mut data2 = WithPinnedBoxFuture { + fut: Box::pin(ready(77)), + }; + assert_eq!(kp.get_mut_or_else(Some(&mut data2), || 100).await, 77); +}