diff --git a/Cargo.toml b/Cargo.toml index 5812913..6c36a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-key-paths" -version = "2.9.0" +version = "2.9.1" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" @@ -38,56 +38,6 @@ tagged_core = ["tagged-core/default"] tokio = ["dep:tokio"] [dev-dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -parking_lot = "0.12" 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"] } -tokio = { version = "1.38.0", features = ["sync", "rt", "rt-multi-thread", "macros"] } -key-paths-derive = { path = "key-paths-derive" } -key-paths-iter = { path = "key-paths-iter", features = ["rayon", "gpu"] } -num_cpus = "1.16" -rayon = "1.10" pin-project = "1.1" - -[[bench]] -name = "deep_chain_leaf" -harness = false - -[[bench]] -name = "kp_plain_only" -harness = false - -[[bench]] -name = "deep_chain_sync_only" -harness = false - -[[bench]] -name = "deep_chain_async_only" -harness = false - -[[bench]] -name = "keypath_vs_unwrap" -harness = false - -[[bench]] -name = "ten_level_arc_rwlock" -harness = false - -[[bench]] -name = "ten_level_std_rwlock" -harness = false - -[[bench]] -name = "ten_level_tokio_rwlock" -harness = false - -[[bench]] -name = "scale_par_bench" -harness = false - -[[bench]] -name = "akp_cpu_bench" -harness = false +key-paths-derive = { path = "key-paths-derive" } diff --git a/benches/BENCHMARK_RESULTS_dynamic_dispatch.md b/benches/BENCHMARK_RESULTS_dynamic_dispatch.md deleted file mode 100644 index 585c6aa..0000000 --- a/benches/BENCHMARK_RESULTS_dynamic_dispatch.md +++ /dev/null @@ -1,84 +0,0 @@ -# Benchmark Results - Updated (No Object Creation Per Iteration) - -## Summary - -All benchmarks have been updated to measure only the `get()`/`get_mut()` call timing, excluding object creation overhead. Write operations now create the instance once per benchmark run, not on each iteration. - -## Performance Results - -| Operation | KeyPath | Direct Unwrap | Overhead/Speedup | Notes | -|-----------|---------|---------------|------------------|-------| -| **Read (3 levels)** | 561.93 ps | 384.73 ps | **1.46x slower** (46% overhead) ⚡ | Read access through nested Option chain | -| **Write (3 levels)** | 4.149 ns | 382.07 ps | **10.9x slower** | Write access through nested Option chain | -| **Deep Read (5 levels, no enum)** | 8.913 ns | 382.83 ps | **23.3x slower** | Deep nested Option chain without enum | -| **Deep Read (5 levels, with enum)** | 9.597 ns | 383.03 ps | **25.1x slower** | Deep nested access with enum case path | -| **Write Deep (with enum)** | 9.935 ns | 381.99 ps | **26.0x slower** | Write access with enum case path | -| **Reused Read** | 390.15 ps | 36.540 ns | **93.6x faster** ⚡ | Multiple accesses with same keypath | -| **Creation (one-time)** | 542.20 ns | N/A | One-time cost | Keypath creation overhead | -| **Pre-composed** | 561.88 ps | N/A | Optimal | Pre-composed keypath access | -| **Composed on-fly** | 215.89 ns | N/A | 384x slower than pre-composed | On-the-fly composition | -| **Ten Level Read** | 891.36 ps | 398.23 ps | **2.24x slower** | 10-level deep nested Option chain read | -| **Ten Level Write** | 21.429 ns | 19.900 ns | **1.08x slower** (essentially identical) ⚡ | 10-level deep nested Option chain write | - -## Key Observations - -### Write Operations Analysis - -**Important Finding**: Write operations now show **higher overhead** (13.1x and 28.1x) compared to the previous results (0.15% overhead). This is because: - -1. **Previous benchmark**: Included object creation (`SomeComplexStruct::new()`) in each iteration, which masked the keypath overhead -2. **Current benchmark**: Only measures `get_mut()` call, revealing the true overhead - -**Why write operations are slower than reads:** -- `get_mut()` requires mutable references, which have stricter borrowing rules -- The compiler optimizes immutable reference chains (`&`) better than mutable reference chains (`&mut`) -- Dynamic dispatch overhead is more visible when not masked by object creation - -### Read Operations - -Read operations show consistent ~2.5x overhead, which is expected: -- Absolute difference: ~560 ps (0.56 ns) - still negligible for most use cases -- The overhead comes from: - - Arc indirection (~1-2 ps) - - Dynamic dispatch (~2-3 ps) - - Closure composition with `and_then` (~200-300 ps) - - Compiler optimization limitations (~200-300 ps) - -### Reuse Performance - -**Key finding**: When keypaths are reused, they are **95.4x faster** than repeated direct unwraps: -- Keypath reused: 381.99 ps per access -- Direct unwrap repeated: 36.45 ns per access -- **This is the primary benefit of KeyPaths** - -## Comparison with Previous Results - -| Metric | Before Optimizations | After Optimizations (Rc + Phase 1&3) | Latest (Corrected Bench) | Improvement | -|--------|---------------------|--------------------------------------|------------------------|-------------| -| Read (3 levels) | 988.69 ps (2.57x) | 565.84 ps (1.43x) | 565.44 ps (1.46x) | **43% improvement** ⚡ | -| Write (3 levels) | 5.04 ns (13.1x) | 4.168 ns (10.8x) | 4.105 ns (10.7x) | **19% improvement** | -| Deep Read | 974.13 ps (2.54x) | 569.35 ps (1.45x) | 9.565 ns (24.5x) | **Corrected: uses _fr + _r** | -| Write Deep | 10.71 ns (28.1x) | 10.272 ns (25.5x) | 9.743 ns (25.0x) | **9% improvement** | -| Reused Read | 381.99 ps (95.4x faster) | 383.74 ps (98.3x faster) | 568.07 ps (65.7x faster) | Consistent benefit | -| Pre-composed | ~956 ps | 558.76 ps | 568.07 ps | **41% improvement** ⚡ | - -**Note**: The `deep_nested_with_enum` benchmark was corrected to use `_fr` (FailableReadable) with `_r` (ReadableEnum) for proper composition compatibility, showing 24.5x overhead due to enum case path matching and Box adapter complexity. - -## Recommendations - -1. **For read operations**: Overhead is now minimal (1.43x, ~170 ps absolute difference) - **44% improvement!** -2. **For write operations**: Overhead is visible (10.8x) but still small in absolute terms (~3.8 ns) -3. **Best practice**: **Reuse keypaths** whenever possible to get the 98.3x speedup -4. **Pre-compose keypaths** before loops/iterations (390x faster than on-the-fly composition) -5. **Optimizations applied**: Phase 1 (direct match) + Rc migration significantly improved performance - -## Conclusion - -The updated benchmarks now accurately measure keypath access performance: -- **Read operations**: ~2.5x overhead, but absolute difference is < 1 ns -- **Write operations**: ~13-28x overhead, but absolute difference is 5-11 ns -- **Reuse advantage**: **95x faster** when keypaths are reused - this is the primary benefit -- **Zero-cost abstraction**: When used optimally (pre-composed and reused), KeyPaths provide massive performance benefits - -The performance overhead for single-use operations is still negligible for most use cases, and the reuse benefits are substantial. - diff --git a/benches/BENCHMARK_SUMMARY_dd_rc_migration.md b/benches/BENCHMARK_SUMMARY_dd_rc_migration.md deleted file mode 100644 index 6065435..0000000 --- a/benches/BENCHMARK_SUMMARY_dd_rc_migration.md +++ /dev/null @@ -1,281 +0,0 @@ -# KeyPaths vs Direct Unwrap - Performance Benchmark Summary - -## Overview - -This document summarizes the performance comparison between KeyPaths and direct nested unwraps based on the benchmarks in `keypath_vs_unwrap.rs`. - -## Quick Start - -```bash -# Run all benchmarks -cargo bench --bench keypath_vs_unwrap - -# Quick test run -cargo bench --bench keypath_vs_unwrap -- --quick - -# View HTML reports -open target/criterion/keypath_vs_unwrap/read_nested_option/report/index.html -``` - -## Benchmark Results Summary - -### 1. Read Nested Option -**Scenario**: Reading through 3 levels of nested `Option` types - -**Findings**: -- KeyPaths: **988.69 ps** (mean) [973.59 ps - 1.0077 ns] -- Direct Unwrap: **384.64 ps** (mean) [380.80 ps - 390.72 ps] -- **Overhead**: **157% slower** (2.57x slower) -- **Note**: Both are extremely fast (sub-nanosecond), overhead is negligible in absolute terms - -**Conclusion**: KeyPaths are slightly slower for single reads, but the absolute difference is minimal (< 1ns). The overhead is acceptable given the type safety benefits. - -### 2. Write Nested Option -**Scenario**: Writing through 3 levels of nested `Option` types - -**Findings**: -- KeyPaths: **333.05 ns** (mean) [327.20 ns - 340.03 ns] -- Direct Unwrap: **332.54 ns** (mean) [328.06 ns - 337.18 ns] -- **Overhead**: **0.15% slower** (essentially identical) - -**Conclusion**: **KeyPaths perform identically to direct unwraps for write operations** - this is excellent performance! - -### 3. Deep Nested with Enum -**Scenario**: Deep nested access including enum case paths and Box adapter - -**Findings**: -- KeyPaths: **964.77 ps** (mean) [961.07 ps - 969.28 ps] -- Direct Unwrap: **387.84 ps** (mean) [382.85 ps - 394.75 ps] -- **Overhead**: **149% slower** (2.49x slower) -- **Note**: Both are sub-nanosecond, absolute overhead is < 1ns - -**Conclusion**: Even with enum case paths and Box adapters, KeyPaths maintain excellent performance with minimal absolute overhead. - -### 4. Write Deep Nested with Enum -**Scenario**: Writing through deep nested structures with enum pattern matching - -**Findings**: -- KeyPaths: **349.18 ns** (mean) [334.99 ns - 371.36 ns] -- Direct Unwrap: **324.25 ns** (mean) [321.26 ns - 327.49 ns] -- **Overhead**: **7.7% slower** - -**Conclusion**: KeyPaths show a small overhead (~25ns) for complex write operations with enums, but this is still excellent performance for the type safety and composability benefits. - -### 5. Keypath Creation -**Scenario**: Creating a complex composed keypath - -**Findings**: -- Creation time: **550.66 ns** (mean) [547.89 ns - 554.06 ns] -- **Note**: This is a one-time cost per keypath creation - -**Conclusion**: Keypath creation has minimal overhead (~550ns) and is typically done once. This cost is amortized over all uses of the keypath. - -### 6. Keypath Reuse ⚡ -**Scenario**: Reusing the same keypath across 100 instances vs repeated unwraps - -**Findings**: -- KeyPath Reused: **383.53 ps** per access (mean) [383.32 ps - 383.85 ps] -- Direct Unwrap Repeated: **37.843 ns** per access (mean) [37.141 ns - 38.815 ns] -- **Speedup**: **98.7x faster** when reusing keypaths! 🚀 - -**Conclusion**: **This is the killer feature!** KeyPaths are **98.7x faster** when reused compared to repeated direct unwraps. This makes them ideal for loops, iterations, and repeated access patterns. - -### 7. Composition Overhead -**Scenario**: Pre-composed vs on-the-fly keypath composition - -**Findings**: -- Pre-composed: **967.13 ps** (mean) [962.24 ps - 976.17 ps] -- Composed on-fly: **239.88 ns** (mean) [239.10 ns - 240.74 ns] -- **Overhead**: **248x slower** when composing on-the-fly - -**Conclusion**: **Always pre-compose keypaths when possible!** Pre-composed keypaths are 248x faster than creating them on-the-fly. Create keypaths once before loops/iterations for optimal performance. - -### 8. Ten Level Deep Nested Access -**Scenario**: Reading and writing through 10 levels of nested `Option` types - -**Findings**: -- **Read**: KeyPaths: **891.36 ps** (mean) [873.38 ps - 915.11 ps] -- **Read**: Direct Unwrap: **398.23 ps** (mean) [393.52 ps - 403.50 ps] -- **Read Overhead**: **2.24x slower** (124% overhead) -- **Write**: KeyPaths: **21.429 ns** (mean) [20.210 ns - 23.626 ns] -- **Write**: Direct Unwrap: **19.900 ns** (mean) [19.677 ns - 20.145 ns] -- **Write Overhead**: **1.08x slower** (8% overhead, essentially identical) ⚡ - -**Conclusion**: -- **Read operations**: Show 2.24x overhead for 10-level deep access, but absolute difference is still minimal (~493 ps) -- **Write operations**: **Excellent performance!** Only 1.08x overhead (8%), essentially identical to direct unwraps. This demonstrates that KeyPaths scale well even for very deep nesting. -- The write performance is particularly impressive - even at 10 levels deep, KeyPaths maintain near-zero overhead for write operations. - -## Key Insights - -### ✅ KeyPaths Advantages - -1. **Reusability**: When a keypath is reused, it's **98.7x faster** than repeated unwraps (383.53 ps vs 37.843 ns) -2. **Type Safety**: Compile-time guarantees prevent runtime errors -3. **Composability**: Easy to build complex access paths -4. **Maintainability**: Clear, declarative code -5. **Write Performance**: Identical performance to direct unwraps (0.15% overhead) - -### ⚠️ Performance Considerations - -1. **Creation Cost**: 550.66 ns to create a complex keypath (one-time cost, amortized over uses) -2. **Single Read Use**: ~2.5x slower for single reads, but absolute overhead is < 1ns -3. **Composition**: Pre-compose keypaths (248x faster than on-the-fly composition) -4. **Deep Writes**: 7.7% overhead for complex enum writes (~25ns absolute difference) - -### 🎯 Best Practices - -1. **Reuse KeyPaths**: Create once, use many times -2. **Pre-compose**: Build complex keypaths before loops/iterations -3. **Profile First**: For performance-critical code, measure before optimizing -4. **Type Safety First**: The safety benefits often outweigh minimal performance costs - -## Performance Characteristics - -| Operation | KeyPath | Direct Unwrap | Overhead/Speedup | -|-----------|---------|---------------|------------------| -| Single Read (3 levels) | 561.93 ps | 384.73 ps | 46% slower (1.46x) ⚡ | -| Single Write (3 levels) | 4.149 ns | 382.07 ps | 10.9x slower | -| Deep Read (5 levels, no enum) | 8.913 ns | 382.83 ps | 23.3x slower | -| Deep Read (5 levels, with enum) | 9.597 ns | 383.03 ps | 25.1x slower | -| Deep Write (with enum) | 9.935 ns | 381.99 ps | 26.0x slower | -| **Reused Read** | **390.15 ps** | **36.540 ns** | **93.6x faster** ⚡ | -| Creation (one-time) | 542.20 ns | N/A | One-time cost | -| Pre-composed | 561.88 ps | N/A | Optimal | -| Composed on-fly | 215.89 ns | N/A | 384x slower than pre-composed | -| **Ten Level Read** | **891.36 ps** | **398.23 ps** | **2.24x slower** | -| **Ten Level Write** | **21.429 ns** | **19.900 ns** | **1.08x slower** (essentially identical) ⚡ | - -## Performance After Optimizations (Rc + Phase 1 & 3) - -### Key Observation -- **Read operations**: 46% overhead (1.46x slower) - **Significantly improved from 2.57x!** ⚡ -- **Write operations**: 10.7x overhead (4.11 ns vs 383 ps) - Measured correctly without object creation -- **Deep nested (5 levels, no enum)**: 23.3x overhead (8.91 ns vs 383 ps) - Pure Option chain -- **Deep nested (5 levels, with enum)**: 25.1x overhead (9.60 ns vs 383 ps) - Includes enum case path + Box adapter -- **Reuse advantage**: **65.7x faster** when keypaths are reused - This is the primary benefit - -### Root Causes - -#### 1. **Compiler Optimizations for Mutable References** -The Rust compiler and LLVM can optimize mutable reference chains (`&mut`) more aggressively than immutable reference chains (`&`) because: -- **Unique ownership**: `&mut` references guarantee no aliasing, enabling aggressive optimizations -- **Better inlining**: Mutable reference chains are easier for the compiler to inline -- **LLVM optimizations**: Mutable reference operations are better optimized by LLVM's optimizer - -#### 2. **Closure Composition Overhead** ✅ **OPTIMIZED** -After Phase 1 optimization, `and_then` has been replaced with direct `match` statements: -```rust -// Optimized (Phase 1) -match f1(r) { - Some(m) => f2(m), - None => None, -} -``` - -This optimization reduced read overhead from **2.57x to 1.46x** (43% improvement)! - -#### 3. **Dynamic Dispatch Overhead** ✅ **OPTIMIZED** -After migration to `Rc` (removed `Send + Sync`): -- **Rc is faster than Arc** for single-threaded use (no atomic operations) -- Reduced indirection overhead -- Better compiler optimizations possible - -#### 4. **Branch Prediction** -Write operations may have better branch prediction patterns, though this is hardware-dependent. - -### Performance Breakdown (After Optimizations) - -**Read Operation (564.20 ps) - Improved from 988.69 ps:** -- Rc dereference: ~0.5-1 ps (faster than Arc) -- Dynamic dispatch: ~1-2 ps (optimized) -- Closure composition (direct match): ~50-100 ps ✅ **Optimized from 200-300 ps** -- Compiler optimization: ~100-150 ps ✅ **Improved from 200-300 ps** -- Option handling: ~50-100 ps -- **Total overhead**: ~178 ps (1.46x slower) - **43% improvement!** - -**Write Operation (4.149 ns) - Correctly measured:** -- Rc dereference: ~0.1-0.2 ns -- Dynamic dispatch: ~0.5-1.0 ns -- Closure composition (direct match): ~0.5-1.0 ns -- Borrowing checks: ~0.5-1.0 ns -- Compiler optimization limitations: ~1.0-2.0 ns -- **Total overhead**: ~3.77 ns (10.8x slower) - -### Deep Nested Comparison (5 Levels) - -**Without Enum (8.913 ns vs 382.83 ps) - 23.3x slower:** -- Pure Option chain: scsf → sosf → omsf_deep → level4_field → level5_field -- Overhead from: 5 levels of closure composition + dynamic dispatch -- No enum case path matching overhead - -**With Enum (9.597 ns vs 383.03 ps) - 25.1x slower:** -- Includes enum case path: scsf → sosf → omse → enum_case → dsf -- Additional overhead from: enum case path matching + Box adapter -- **~7% slower** than pure Option chain due to enum complexity - -**Key Insight**: Enum case paths add minimal overhead (~7%) compared to pure Option chains, making them a viable option for complex nested structures. - -### Improvement Plan - -See **[PERFORMANCE_ANALYSIS.md](./PERFORMANCE_ANALYSIS.md)** for a detailed analysis and improvement plan. The plan includes: - -1. **Phase 1**: Optimize closure composition (replace `and_then` with direct matching) - - Expected: 20-30% faster reads -2. **Phase 2**: Specialize for common cases - - Expected: 15-25% faster reads -3. **Phase 3**: Add inline hints and compiler optimizations - - Expected: 10-15% faster reads -4. **Phase 4**: Reduce Arc indirection where possible - - Expected: 5-10% faster reads -5. **Phase 5**: Compile-time specialization (long-term) - - Expected: 30-40% faster reads - -**Target**: Reduce read overhead from 2.57x to < 1.5x (ideally < 1.2x) - -### Current Status - -While read operations show higher relative overhead, the **absolute difference is < 1ns**, which is negligible for most use cases. The primary benefit of KeyPaths comes from: -- **Reuse**: 98.7x faster when reused -- **Type safety**: Compile-time guarantees -- **Composability**: Easy to build complex access patterns - -For write operations, KeyPaths are already essentially **zero-cost**. - -## Conclusion - -KeyPaths provide: -- **Minimal overhead** for single-use operations (0-8% for writes, ~150% for reads but absolute overhead is < 1ns) -- **Massive speedup** when reused (**98.7x faster** than repeated unwraps) -- **Type safety** and **maintainability** benefits -- **Zero-cost abstraction** when used optimally (pre-composed and reused) - -**Key Findings** (After Optimizations): -1. ✅ **Read operations**: Significantly improved! Only 46% overhead (1.46x) vs previous 2.57x -2. ✅ **Write operations**: 10.7x overhead when measured correctly (without object creation) -3. ⚠️ **Deep nested (5 levels)**: 23.3x overhead without enum, 25.1x with enum (enum adds ~7% overhead) -4. 🚀 **Reuse advantage**: **65.7x faster** when keypaths are reused - this is the primary benefit -5. ⚡ **Optimizations**: Phase 1 (direct match) + Rc migration improved read performance by 43% -6. ⚠️ **Composition**: Pre-compose keypaths (378x faster than on-the-fly composition) - -**Recommendation**: -- Use KeyPaths for their safety and composability benefits -- **Pre-compose keypaths** before loops/iterations (378x faster than on-the-fly) -- **Reuse keypaths** whenever possible to get the 65.7x speedup -- Read operations now have minimal overhead (1.46x, ~178 ps absolute difference) -- Write operations have higher overhead (10.7x) but absolute difference is still small (~3.72 ns) -- Deep nested paths show higher overhead (23.3x without enum, 25.1x with enum) but are still manageable for most use cases -- **Ten-level deep writes show excellent performance** (1.08x overhead, essentially identical to direct unwraps) ⚡ -- Enum case paths add ~7% overhead compared to pure Option chains -- **Optimizations applied**: Phase 1 (direct match) + Rc migration = 43% read performance improvement - -## Running Full Benchmarks - -For detailed statistical analysis and HTML reports: - -```bash -cargo bench --bench keypath_vs_unwrap -``` - -Results will be in `target/criterion/keypath_vs_unwrap/` with detailed HTML reports for each benchmark. - diff --git a/benches/PERFORMANCE_ANALYSIS_optimization_phases.md b/benches/PERFORMANCE_ANALYSIS_optimization_phases.md deleted file mode 100644 index 925652b..0000000 --- a/benches/PERFORMANCE_ANALYSIS_optimization_phases.md +++ /dev/null @@ -1,346 +0,0 @@ -# Performance Analysis: KeyPath Performance Characteristics - -## Executive Summary - -**Updated Benchmark Results** (measuring only `get()`/`get_mut()` calls, excluding object creation): - -Benchmark results show that **write operations have higher overhead (13.1x-28.1x)** than read operations (2.45x-2.54x) when measured correctly. Previous results masked write overhead by including object creation in each iteration. This document explains the performance characteristics and provides a plan to improve performance. - -## Current Benchmark Results (After Optimizations - Latest) - -| Operation | KeyPath | Direct Unwrap | Overhead | Notes | -|-----------|---------|---------------|----------|-------| -| **Read (3 levels)** | 565.44 ps | 387.89 ps | **1.46x slower** (46% overhead) ⚡ | Read access through nested Option chain | -| **Write (3 levels)** | 4.105 ns | 383.28 ps | **10.7x slower** | Write access through nested Option chain | -| **Deep Read (with enum)** | 9.565 ns | 390.12 ps | **24.5x slower** | Deep nested access with enum case path (corrected benchmark) | -| **Write Deep (with enum)** | 9.743 ns | 389.16 ps | **25.0x slower** | Write access with enum case path | -| **Reused Read** | 568.07 ps | 37.296 ns | **65.7x faster** ⚡ | Multiple accesses with same keypath | -| **Ten Level Read** | 891.36 ps | 398.23 ps | **2.24x slower** (124% overhead) | 10-level deep nested Option chain read | -| **Ten Level Write** | 21.429 ns | 19.900 ns | **1.08x slower** (8% overhead, essentially identical) ⚡ | 10-level deep nested Option chain write | - -**Key Findings** (After Phase 1 & 3 Optimizations + Rc Migration): -- **Read operations**: **43% improvement!** Now only 1.46x overhead (was 2.45x), absolute difference ~178 ps -- **Write operations**: 19% improvement! Now 10.7x overhead (was 13.1x), absolute difference ~3.72 ns -- **Deep nested with enum**: Shows 24.5x overhead due to enum case path + Box adapter complexity -- **Reuse advantage**: **65.7x faster** when keypaths are reused - this is the primary benefit -- **Optimizations applied**: Phase 1 (direct match) + Rc migration = significant performance gains - -**Note on Deep Nested Benchmark**: The corrected `bench_deep_nested_with_enum` uses `_fr` (FailableReadable) with `_r` (ReadableEnum) for proper composition, showing 24.5x overhead due to enum case path matching and Box adapter complexity. - -## Root Cause Analysis - -### 1. **Rc Indirection Overhead** ✅ **OPTIMIZED** - -After migration, both read and write operations use `Rc` for type erasure: - -```rust -// Read -FailableReadable(Rc Fn(&'a Root) -> Option<&'a Value>>) - -// Write -FailableWritable(Rc Fn(&'a mut Root) -> Option<&'a mut Value>>) -``` - -**Impact**: Rc is faster than Arc for single-threaded use (no atomic operations), reducing overhead by ~0.5-1 ps per access. - -### 2. **Dynamic Dispatch (Trait Object) Overhead** - -Both use dynamic dispatch through trait objects: - -```rust -// In get() method -KeyPaths::FailableReadable(f) => f(root), // Dynamic dispatch - -// In get_mut() method -KeyPaths::FailableWritable(f) => f(root), // Dynamic dispatch -``` - -**Impact**: Both have similar dynamic dispatch overhead (~1-2ns), so this is also not the primary cause. - -### 3. **Composition Closure Structure** ✅ **OPTIMIZED (Phase 1)** - -After Phase 1 optimization, composed keypaths use direct `match` instead of `and_then`: - -#### Read Composition (Optimized) -```rust -// Optimized (Phase 1) -(FailableReadable(f1), FailableReadable(f2)) => { - let f1 = f1.clone(); - let f2 = f2.clone(); - FailableReadable(Rc::new(move |r| { - match f1(r) { - Some(m) => f2(m), - None => None, - } - })) -} -``` - -**Execution path for reads (optimized):** -1. Call `f1(r)` → returns `Option<&Mid>` -2. Direct `match` statement (no closure creation) ✅ -3. Call `f2(m)` → returns `Option<&Value>` - -**Overhead reduction**: Direct `match` eliminates closure creation overhead, reducing composition cost by ~150-200 ps. - -#### Write Composition (Faster) -```rust -// From compose() method -(FailableWritable(f1), FailableWritable(f2)) => { - FailableWritable(Arc::new(move |r| f1(r).and_then(|m| f2(m)))) -} -``` - -**Execution path for writes:** -1. Call `f1(r)` → returns `Option<&mut Mid>` -2. Call `and_then(|m| f2(m))` → **creates a closure** `|m| f2(m)` -3. Execute closure with `m: &mut Mid` -4. Call `f2(m)` → returns `Option<&mut Value>` - -**Why writes show higher overhead**: Despite compiler optimizations for mutable references, write operations show higher overhead because: -- **Stricter borrowing rules**: `&mut` references have unique ownership, which adds runtime checks -- **Less optimization opportunity**: The compiler can optimize direct unwraps better than keypath chains for mutable references -- **Dynamic dispatch overhead**: More visible when not masked by object creation -- **Closure chain complexity**: Mutable reference closures are harder to optimize through dynamic dispatch - -### 4. **Option Handling** - -Both use `Option` wrapping, but the overhead is similar: -- Read: `Option<&Value>` -- Write: `Option<&mut Value>` - -**Impact**: Similar overhead, not the primary cause. - -### 5. **Compiler Optimizations** - -The Rust compiler and LLVM can optimize mutable reference chains more aggressively: - -```rust -// Direct unwrap (optimized by compiler) -if let Some(sos) = instance.scsf.as_mut() { - if let Some(oms) = sos.sosf.as_mut() { - if let Some(omsf) = oms.omsf.as_mut() { - // Compiler can inline and optimize this chain - } - } -} - -// Keypath (harder to optimize) -keypath.get_mut(&mut instance) // Dynamic dispatch + closure chain -``` - -**For writes**: The compiler has difficulty optimizing mutable reference chains through keypaths because: -- Dynamic dispatch prevents inlining of the closure chain -- Mutable reference uniqueness checks add runtime overhead -- The compiler can optimize direct unwraps much better than keypath chains -- Borrowing rules are enforced at runtime, adding overhead - -**For reads**: The compiler has similar difficulty, but reads are faster because: -- Immutable references don't require uniqueness checks -- Less runtime overhead from borrowing rules -- Still limited by dynamic dispatch and closure chain complexity - -## Detailed Performance Breakdown - -### Read Operation Overhead (944.68 ps vs 385.00 ps) - -**Overhead components:** -1. **Arc dereference**: ~1-2 ps -2. **Dynamic dispatch**: ~2-3 ps -3. **Closure creation in `and_then`**: ~200-300 ps ⚠️ **Main contributor** -4. **Multiple closure executions**: ~100-200 ps -5. **Option handling**: ~50-100 ps -6. **Compiler optimization limitations**: ~200-300 ps ⚠️ **Main contributor** - -**Total overhead**: ~560 ps (2.45x slower, but absolute difference is only ~560 ps = 0.56 ns) - -**Note**: Even with 2.45x overhead, the absolute difference is < 1ns, which is negligible for most use cases. - -### Write Operation Overhead (4.168 ns vs 384.47 ps) - **17% IMPROVEMENT!** - -**Overhead components (after optimizations):** -1. **Rc dereference**: ~0.05-0.1 ns ✅ (faster than Arc) -2. **Dynamic dispatch**: ~0.5-1.0 ns -3. **Closure composition (direct match)**: ~0.5-1.0 ns ✅ **Optimized from 1.0-1.5 ns** -4. **Multiple closure executions**: ~0.3-0.5 ns ✅ (optimized) -5. **Option handling**: ~0.2-0.5 ns -6. **Borrowing checks**: ~0.5-1.0 ns (mutable reference uniqueness checks) -7. **Compiler optimization limitations**: ~1.0-2.0 ns - -**Total overhead**: ~3.78 ns (10.8x slower) - **17% improvement from 13.1x!** - -**Key Insight**: Write operations still show higher overhead than reads, but optimizations have improved performance: -- Direct `match` reduces closure composition overhead -- Rc migration reduces indirection overhead -- The compiler can optimize direct unwraps better than keypath chains for mutable references -- Borrowing rules add runtime overhead that's more visible - -## Improvement Plan - -### Phase 1: Optimize Closure Composition (High Impact) - -**Problem**: The `and_then` closure in composition creates unnecessary overhead. - -**Solution**: Use direct function composition where possible: - -```rust -// Current (slower) -FailableReadable(Arc::new(move |r| f1(r).and_then(|m| f2(m)))) - -// Optimized (faster) -FailableReadable(Arc::new({ - let f1 = f1.clone(); - let f2 = f2.clone(); - move |r| { - match f1(r) { - Some(m) => f2(m), - None => None, - } - } -})) -``` - -**Expected improvement**: 20-30% faster reads - -### Phase 2: Specialize for Common Cases (Medium Impact) - -**Problem**: Generic composition handles all cases but isn't optimized for common patterns. - -**Solution**: Add specialized composition methods for common patterns: - -```rust -// Specialized for FailableReadable chains -impl KeyPaths { - #[inline] - pub fn compose_failable_readable_chain( - self, - mid: KeyPaths - ) -> KeyPaths - where - Self: FailableReadable, - KeyPaths: FailableReadable, - { - // Direct composition without and_then overhead - } -} -``` - -**Expected improvement**: 15-25% faster reads - -### Phase 3: Inline Hints and Compiler Optimizations (Medium Impact) - -**Problem**: Compiler can't inline through dynamic dispatch. - -**Solution**: -1. Add `#[inline(always)]` to hot paths -2. Use `#[inline]` more aggressively -3. Consider using `#[target_feature]` for specific optimizations - -```rust -#[inline(always)] -pub fn get<'a>(&'a self, root: &'a Root) -> Option<&'a Value> { - match self { - KeyPaths::FailableReadable(f) => { - #[inline(always)] - let result = f(root); - result - }, - // ... - } -} -``` - -**Expected improvement**: 10-15% faster reads - -### Phase 4: Reduce Arc Indirection (Low-Medium Impact) - -**Problem**: Arc adds indirection overhead. - -**Solution**: Consider using `Rc` for single-threaded cases or direct function pointers for simple cases: - -```rust -// For single-threaded use cases -enum KeyPaths { - FailableReadableRc(Rc Fn(&'a Root) -> Option<&'a Value>>), - // ... -} - -// Or use function pointers for non-capturing closures -enum KeyPaths { - FailableReadableFn(fn(&Root) -> Option<&Value>), - // ... -} -``` - -**Expected improvement**: 5-10% faster reads - -### Phase 5: Compile-Time Specialization (High Impact, Complex) - -**Problem**: Generic code can't be specialized at compile time. - -**Solution**: Use const generics or macros to generate specialized code: - -```rust -// Macro to generate specialized composition -macro_rules! compose_failable_readable { - ($f1:expr, $f2:expr) => {{ - // Direct composition without and_then - Arc::new(move |r| { - if let Some(m) = $f1(r) { - $f2(m) - } else { - None - } - }) - }}; -} -``` - -**Expected improvement**: 30-40% faster reads - -## Implementation Priority - -1. **Phase 1** (High Impact, Low Complexity) - **Start here** -2. **Phase 3** (Medium Impact, Low Complexity) - **Quick wins** -3. **Phase 2** (Medium Impact, Medium Complexity) -4. **Phase 5** (High Impact, High Complexity) - **Long-term** -5. **Phase 4** (Low-Medium Impact, Medium Complexity) - -## Optimization Results ✅ **ACHIEVED** - -| Operation | Before | After Phase 1 & 3 + Rc | Improvement | -|-----------|--------|------------------------|-------------| -| **Read (3 levels)** | 944.68 ps (2.45x) | 565.84 ps (1.43x) | **44% improvement** ⚡ | -| **Write (3 levels)** | 5.04 ns (13.1x) | 4.168 ns (10.8x) | **17% improvement** | -| **Deep Read** | 974.13 ps (2.54x) | 569.35 ps (1.45x) | **42% improvement** ⚡ | -| **Write Deep** | 10.71 ns (28.1x) | 10.272 ns (25.5x) | **4% improvement** | - -**Targets Achieved**: -- ✅ Read overhead reduced from 2.45x to 1.43x (target was < 1.5x) - **EXCEEDED!** -- ⚠️ Write overhead reduced from 13.1x to 10.8x (target was < 5x) - **Partially achieved** - -## Conclusion - -The optimizations have been **successfully implemented** with significant performance improvements: - -1. **Read operations**: **44% improvement!** Now only 1.43x overhead (was 2.45x) - - Absolute difference: ~170 ps (0.17 ns) - negligible - - Primary improvements: Direct `match` (Phase 1) + Rc migration - - **Target exceeded**: Achieved < 1.5x (target was < 1.5x) - -2. **Write operations**: **17% improvement!** Now 10.8x overhead (was 13.1x) - - Absolute difference: ~3.8 ns - still small - - Primary improvements: Direct `match` (Phase 1) + Rc migration - - **Partially achieved**: Reduced but still above < 5x target - -3. **Reuse advantage**: **98.3x faster** when keypaths are reused - this is the primary benefit - - KeyPaths excel when reused across multiple instances - - Pre-compose keypaths before loops/iterations (390x faster than on-the-fly) - -4. **Optimizations Applied**: - - ✅ **Phase 1**: Replaced `and_then` with direct `match` statements - - ✅ **Phase 3**: Added `#[inline(always)]` to hot paths - - ✅ **Rc Migration**: Replaced `Arc` with `Rc` (removed `Send + Sync`) - -**Key Takeaway**: The optimizations have significantly improved read performance (44% improvement), bringing overhead down to just 1.43x. Write operations also improved (17%), though they still show higher overhead. The primary benefit of KeyPaths remains **reuse** (98.3x faster), making them a zero-cost abstraction when used optimally. - diff --git a/benches/README.md b/benches/README.md deleted file mode 100644 index c96816e..0000000 --- a/benches/README.md +++ /dev/null @@ -1,246 +0,0 @@ -# KeyPaths Performance Benchmarks - -This directory contains comprehensive benchmarks comparing the performance of KeyPaths versus direct nested unwraps. - -## Running Benchmarks - -### Quick Run -```bash -# Benchmark nested Option access -cargo bench --bench keypath_vs_unwrap - -# Benchmark RwLock write operations with deeply nested structures -cargo bench --bench rwlock_write_deeply_nested -``` - -### Using the Script -```bash -./benches/run_benchmarks.sh -``` - -## Benchmark Suites - -### 1. Read Nested Option (`read_nested_option`) -Compares reading through nested `Option` types: -- **Keypath**: `SomeComplexStruct::scsf_fw().then(...).then(...).get()` -- **Direct**: `instance.scsf.as_ref().and_then(...).and_then(...)` - -### 2. Write Nested Option (`write_nested_option`) -Compares writing through nested `Option` types: -- **Keypath**: `keypath.get_mut(&mut instance)` -- **Direct**: Multiple nested `if let Some(...)` statements - -### 3. Deep Nested with Enum (`deep_nested_with_enum`) -Compares deep nested access including enum case paths: -- **Keypath**: Includes `SomeEnum::b_w()` and `for_box()` adapter -- **Direct**: Pattern matching on enum variants - -### 4. Write Deep Nested with Enum (`write_deep_nested_with_enum`) -Compares writing through deep nested structures with enums: -- **Keypath**: Full composition chain with enum case path -- **Direct**: Nested pattern matching and unwraps - -### 5. Keypath Creation (`keypath_creation`) -Measures the overhead of creating composed keypaths: -- Tests the cost of chaining multiple keypaths together - -### 6. Keypath Reuse (`keypath_reuse`) -Compares performance when reusing the same keypath vs repeated unwraps: -- **Keypath**: Single keypath reused across 100 instances -- **Direct**: Repeated unwrap chains for each instance - -### 7. Composition Overhead (`composition_overhead`) -Compares pre-composed vs on-the-fly composition: -- **Pre-composed**: Keypath created once, reused -- **Composed on-fly**: Keypath created in each iteration - -### 8. RwLock Write Deeply Nested (`rwlock_write_deeply_nested`) -**Use Case**: Demonstrates updating deeply nested values inside `Arc>` structures. - -This benchmark is particularly useful for scenarios where you need to: -- Update nested fields in thread-safe shared data structures -- Avoid manual write guard management and nested unwraps -- Maintain type safety when accessing deeply nested Option fields - -**Example Structure**: -```rust -SomeStruct { - f1: Arc> // Thread-safe shared data - -> SomeOtherStruct { - f4: DeeplyNestedStruct { - f1: Option // Deeply nested field to update - } - } -} -``` - -**Keypath Approach**: -```rust -use keypaths_proc::Kp; -use parking_lot::RwLock; -use std::sync::Arc; - -#[derive(Kp)] -#[Writable] -struct SomeStruct { - f1: Arc>, -} - -// Compose keypath: SomeStruct -> Arc> -> SomeOtherStruct -> DeeplyNestedStruct -> f1 -let keypath = SomeStruct::f1_fw_at( - SomeOtherStruct::f4_w() - .then(DeeplyNestedStruct::f1_w()) -); - -// Use keypath to update value -keypath.get_mut(&instance, |value| { - *value = Some(String::from("new value")); -}); -``` - -**Traditional Approach**: -```rust -// Manual write guard and nested unwraps -let mut guard = instance.f1.write(); -if let Some(ref mut f1) = guard.f4.f1 { - *f1 = String::from("new value"); -} -``` - -**Benchmark Variants**: -- `rwlock_write_deeply_nested`: Write to `f1` (Option) 3 levels deep -- `rwlock_write_deeply_nested_f2`: Write to `f2` (Option) 3 levels deep -- `rwlock_write_f3`: Write to `f3` (Option) 2 levels deep -- `rwlock_multiple_writes`: Multiple sequential writes using keypath vs single/multiple write guards - -**Benchmark Results**: - -### parking_lot::RwLock Benchmarks - -| Benchmark | Approach | Mean Time | Comparison | -|-----------|----------|-----------|------------| -| `rwlock_write_deeply_nested` (String, 3 levels) | Keypath | 24.5 ns | 2.5% slower | -| | Write Guard | 23.9 ns | baseline | -| | Write Guard (nested) | 23.8 ns | **0.4% faster** | -| `rwlock_write_deeply_nested_f2` (i32, 3 levels) | Keypath | 8.5 ns | **1.2% faster** ⚡ | -| | Write Guard | 8.6 ns | baseline | -| `rwlock_write_f3` (String, 2 levels) | Keypath | 23.8 ns | **0.4% faster** ⚡ | -| | Write Guard | 23.9 ns | baseline | -| `rwlock_multiple_writes` (sequential) | Keypath | 55.8 ns | 33.5% slower | -| | Write Guard (single) | 41.8 ns | baseline | -| | Write Guard (multiple) | 56.2 ns | 34.4% slower | - -### tokio::sync::RwLock Benchmarks - -| Benchmark | Approach | Mean Time | Comparison | -|-----------|----------|-----------|------------| -| `tokio_rwlock_read_deeply_nested` (String, 3 levels) | Keypath | 104.8 ns | 0.2% slower | -| | Read Guard | 104.6 ns | baseline | -| `tokio_rwlock_write_deeply_nested` (String, 3 levels) | Keypath | 124.8 ns | 0.6% slower | -| | Write Guard | 124.1 ns | baseline | -| `tokio_rwlock_write_deeply_nested_f2` (i32, 3 levels) | Keypath | 103.8 ns | **1.2% faster** ⚡ | -| | Write Guard | 105.0 ns | baseline | -| `tokio_rwlock_read_f3` (String, 2 levels) | Keypath | 103.3 ns | 0.1% slower | -| | Read Guard | 103.2 ns | baseline | -| `tokio_rwlock_write_f3` (String, 2 levels) | Keypath | 125.7 ns | 0.9% slower | -| | Write Guard | 124.6 ns | baseline | - -**Key Findings**: - -**parking_lot::RwLock:** -- ✅ For single write operations, keypath approach is **essentially identical** (0-2.5% overhead) to manual write guards -- ✅ Simple field writes can be **1.2% faster** with keypaths -- ⚠️ For multiple sequential writes, using a single write guard is more efficient (33% faster) than creating multiple keypaths -- ✅ Keypath approach performs similarly to multiple write guards when doing multiple operations - -**tokio::sync::RwLock:** -- ✅ For async read operations, keypath approach shows **essentially identical performance** (0-0.2% overhead) -- ✅ For async write operations, keypath approach shows **essentially identical performance** (0-1% overhead) -- ✅ Simple async field operations can be **1.2% faster** with keypaths -- ✅ Async operations maintain similar performance characteristics to synchronous operations - -**Overall:** -- ✅ The performance difference is negligible (sub-nanosecond) for most use cases -- ✅ Keypath approach provides significant benefits in type safety, composability, and maintainability with minimal performance cost -- ✅ Both synchronous (`parking_lot`) and asynchronous (`tokio`) primitives show excellent performance with keypaths - -**Benefits of Keypath Approach**: -1. **Type Safety**: Compile-time verification of the access path -2. **Composability**: Easy to build complex nested access paths -3. **Reusability**: Create keypath once, use many times -4. **Readability**: Clear, declarative code that shows the exact path -5. **Maintainability**: Changes to structure automatically caught at compile time - -**When to Use**: -- Thread-safe shared data structures with deep nesting -- Frequent updates to nested fields in concurrent applications -- Complex data access patterns that benefit from composition -- Code that needs to be self-documenting about data access paths - -## Viewing Results - -After running benchmarks, view the HTML reports: - -```bash -# Open the main report directory -open target/criterion/keypath_vs_unwrap/read_nested_option/report/index.html -``` - -Or navigate to `target/criterion/keypath_vs_unwrap/` and open any `report/index.html` file in your browser. - -For RwLock benchmarks: -```bash -open target/criterion/rwlock_write_deeply_nested/rwlock_write_deeply_nested/report/index.html -``` - -## Expected Findings - -### Keypaths Advantages -- **Type Safety**: Compile-time guarantees -- **Reusability**: Create once, use many times -- **Composability**: Easy to build complex access paths -- **Maintainability**: Clear, declarative code - -### Performance Characteristics (After Optimizations) - -**Read Operations:** -- **Overhead**: Only 1.43x (43% slower) - **44% improvement from previous 2.45x!** -- **Absolute difference**: ~170 ps (0.17 ns) - negligible -- **Optimizations**: Direct `match` composition + Rc migration - -**Write Operations:** -- **Overhead**: 10.8x slower - **17% improvement from previous 13.1x** -- **Absolute difference**: ~3.8 ns - still small -- **Optimizations**: Direct `match` composition + Rc migration - -**Reuse Performance:** -- **98.3x faster** when keypaths are reused - this is the primary benefit! -- Pre-composed keypaths are 390x faster than on-the-fly composition - -**Key Optimizations Applied:** -- ✅ Phase 1: Direct `match` instead of `and_then` (eliminated closure overhead) -- ✅ Phase 3: Aggressive inlining with `#[inline(always)]` -- ✅ Rc Migration: Replaced `Arc` with `Rc` (removed `Send + Sync`) - -See [`BENCHMARK_SUMMARY.md`](BENCHMARK_SUMMARY.md) for detailed results and analysis. - -## Interpreting Results - -The benchmarks use Criterion.rs which provides: -- **Mean time**: Average execution time -- **Throughput**: Operations per second -- **Comparison**: Direct comparison between keypath and unwrap approaches -- **Statistical significance**: Confidence intervals and p-values - -Look for: -- **Slower**: Keypath approach is slower (expected for creation) -- **Faster**: Keypath approach is faster (possible with reuse) -- **Similar**: Performance is equivalent (ideal for zero-cost abstraction) - -## Notes - -- Benchmarks run in release mode with optimizations -- Results may vary based on CPU architecture and compiler optimizations -- The `black_box` function prevents compiler optimizations that would skew results -- Multiple iterations ensure statistical significance - diff --git a/benches/akp_cpu_bench.rs b/benches/akp_cpu_bench.rs deleted file mode 100644 index 5b07bd6..0000000 --- a/benches/akp_cpu_bench.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Benchmark: run numeric keypath extraction + transform (same as wgpu shader) on CPU -//! sequential, Rayon parallel, and GPU. Two ways to build the numeric keypath: -//! - **closure**: [numeric_akp_f32] (manual extractor) -//! - **Kp/Pkp**: [IntoNumericAKp] with derived keypath (e.g. `User::score().into_numeric_akp(...)`) -//! -//! Run: `cargo bench --bench akp_cpu_bench` -//! Requires key-paths-iter with features = ["rayon", "gpu"] in dev-dependencies. - -use criterion::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; -use key_paths_derive::Kp; -use key_paths_iter::wgpu::{ - GpuValue, IntoNumericAKp, NumericAKp, WgpuContext, cpu_transform_f32, numeric_akp_f32, -}; -use rayon::prelude::*; -use std::sync::Arc; - -/// Manual struct for closure-based benchmark (no derive). -#[derive(Clone, Debug)] -#[allow(dead_code)] -struct UserManual { - name: String, - score: f32, -} - -/// Same shape with Kp derive for IntoNumericAKp benchmark. -#[derive(Kp, Clone, Debug)] -#[allow(dead_code)] -struct UserKp { - name: String, - score: f32, -} - -fn make_users_manual(n: usize) -> Vec { - (0..n) - .map(|i| UserManual { - name: format!("user_{}", i), - score: (i as f32) * 0.1, - }) - .collect() -} - -fn make_users_kp(n: usize) -> Vec { - (0..n) - .map(|i| UserKp { - name: format!("user_{}", i), - score: (i as f32) * 0.1, - }) - .collect() -} - -// ─── Sequential: one root at a time, extract + CPU transform ────────────────── - -fn run_numeric_sequential(roots: &[impl AsAny], kp: &NumericAKp) -> Vec { - let mut out = Vec::with_capacity(roots.len()); - for root in roots { - let v = (kp.extractor)(root.as_any()); - out.push(match v { - Some(GpuValue::F32(f)) => cpu_transform_f32(f), - _ => 0.0, - }); - } - out -} - -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, - }) - .collect() -} - -fn run_numeric_gpu(roots: &[impl AsAny], kp: &NumericAKp, ctx: &WgpuContext) -> Vec { - let flat: Vec = roots - .iter() - .map(|root| match (kp.extractor)(root.as_any()) { - Some(GpuValue::F32(f)) => f, - _ => 0.0, - }) - .collect(); - ctx.transform_f32_gpu(&flat).unwrap_or(flat) -} - -trait AsAny { - fn as_any(&self) -> &dyn std::any::Any; -} -impl AsAny for UserManual { - fn as_any(&self) -> &dyn std::any::Any { - self - } -} -impl AsAny for UserKp { - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -fn bench_akp_numeric(c: &mut Criterion) { - let score_kp = numeric_akp_f32::(|u| Some(u.score), "input * 2.0 + 1.0"); - let score_kp = Arc::new(score_kp); - let wgpu_ctx = WgpuContext::new().ok(); - - let mut group = c.benchmark_group("akp_numeric_transform_closure"); - group.sample_size(50); - - for n_roots in [1_000_usize, 10_000, 50_000, 100_000] { - group.bench_function(format!("sequential_{}", n_roots), |b| { - b.iter_batched( - || make_users_manual(n_roots), - |roots| run_numeric_sequential(black_box(&roots), black_box(score_kp.as_ref())), - BatchSize::SmallInput, - ); - }); - - group.bench_function(format!("rayon_parallel_{}", n_roots), |b| { - b.iter_batched( - || make_users_manual(n_roots), - |roots| run_numeric_rayon(black_box(&roots), black_box(score_kp.as_ref())), - BatchSize::SmallInput, - ); - }); - - if let Some(ref ctx) = wgpu_ctx { - group.bench_function(format!("gpu_{}", n_roots), |b| { - b.iter_batched( - || make_users_manual(n_roots), - |roots| run_numeric_gpu(black_box(&roots), black_box(score_kp.as_ref()), ctx), - BatchSize::SmallInput, - ); - }); - } - } - group.finish(); - - // Kp derive + IntoNumericAKp (reference-based; same workload) - let score_numeric_kp = UserKp::score().into_numeric_akp("input * 2.0 + 1.0"); - let score_numeric_kp = Arc::new(score_numeric_kp); - - let mut group_kp = c.benchmark_group("akp_numeric_transform_from_kp"); - group_kp.sample_size(50); - - for n_roots in [1_000_usize, 10_000, 50_000, 100_000] { - 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())) - }, - BatchSize::SmallInput, - ); - }); - - group_kp.bench_function(format!("rayon_parallel_{}", n_roots), |b| { - b.iter_batched( - || make_users_kp(n_roots), - |roots| run_numeric_rayon(black_box(&roots), black_box(score_numeric_kp.as_ref())), - BatchSize::SmallInput, - ); - }); - - if let Some(ref ctx) = wgpu_ctx { - 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, - ) - }, - BatchSize::SmallInput, - ); - }); - } - } - group_kp.finish(); -} - -criterion_group!(akp_benches, bench_akp_numeric); -criterion_main!(akp_benches); diff --git a/benches/deep_chain_async_only.rs b/benches/deep_chain_async_only.rs deleted file mode 100644 index ca52ed9..0000000 --- a/benches/deep_chain_async_only.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Benchmark: reading and writing the deepest value (leaf) through async locks only. -//! -//! Structure: Root -> Arc> -> L1 -> L2 -> L3 -> leaf i32 -//! (One async Mutex level, same depth as deep_chain_leaf but async-only) -//! -//! Compares: -//! - Keypath approach: Kp.then_async(AsyncLockKp).then().then() chain -//! - Direct lock approach: tokio_mutex.lock().await, then access leaf - -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; - -// Root -> Arc> -#[derive(Clone)] -struct Root { - tokio_mutex: Arc>, -} - -// L1 -> Level2 (plain) -#[derive(Clone)] -struct Level1 { - inner: Level2, -} - -// L2 -> Level3 (plain) -#[derive(Clone)] -struct Level2 { - inner: Level3, -} - -// L3 -> leaf i32 -#[derive(Clone)] -struct Level3 { - leaf: i32, -} - -fn make_root() -> Root { - Root { - tokio_mutex: Arc::new(tokio::sync::Mutex::new(Level1 { - inner: Level2 { - inner: Level3 { leaf: 42 }, - }, - })), - } -} - -#[inline(never)] -fn build_and_get<'a>(root: &'a Root, rt: &Runtime) -> Option<&'a i32> { - let prev: rust_key_paths::KpType< - Arc>, - Arc>, - > = Kp::new(|t: &_| Some(t), |t: &mut _| Some(t)); - let next: rust_key_paths::KpType = Kp::new( - |l: &Level1| Some(&l.inner.inner.leaf), - |l: &mut Level1| Some(&mut l.inner.inner.leaf), - ); - 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 step1 = kp_root_to_lock.then_async(async_l1); - rt.block_on(step1.get(root)) -} - -#[inline(never)] -fn build_and_get_mut<'a>(root: &'a mut Root, rt: &Runtime) -> Option<&'a mut i32> { - let prev: rust_key_paths::KpType< - Arc>, - Arc>, - > = Kp::new(|t: &_| Some(t), |t: &mut _| Some(t)); - let next: rust_key_paths::KpType = Kp::new( - |l: &Level1| Some(&l.inner.inner.leaf), - |l: &mut Level1| Some(&mut l.inner.inner.leaf), - ); - 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 step1 = kp_root_to_lock.then_async(async_l1); - rt.block_on(step1.get_mut(root)) -} - -fn bench_deep_chain_async_read(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("deep_chain_async_read"); - - group.bench_function("keypath", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let leaf = build_and_get(root_ref, &rt); - black_box(leaf); - }) - }); - - group.bench_function("direct_locks", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - rt.block_on(async { - let guard = root_ref.tokio_mutex.lock().await; - let leaf = &guard.inner.inner.leaf; - black_box(leaf); - }) - }) - }); - - group.finish(); -} - -fn bench_deep_chain_async_write(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("deep_chain_async_write"); - - group.bench_function("keypath", |b| { - let mut root = make_root(); - b.iter(|| { - if let Some(l) = build_and_get_mut(black_box(&mut root), &rt) { - *l = 99; - } - }) - }); - - group.bench_function("direct_locks", |b| { - let mut root = make_root(); - b.iter(|| { - rt.block_on(async { - let mut guard = root.tokio_mutex.lock().await; - guard.inner.inner.leaf = 99; - black_box(&mut guard.inner.inner.leaf); - }) - }) - }); - - group.finish(); -} - -criterion_group!( - benches, - bench_deep_chain_async_read, - bench_deep_chain_async_write, -); -criterion_main!(benches); diff --git a/benches/deep_chain_leaf.rs b/benches/deep_chain_leaf.rs deleted file mode 100644 index 2c184b9..0000000 --- a/benches/deep_chain_leaf.rs +++ /dev/null @@ -1,235 +0,0 @@ -//! Benchmark: reading and writing the deepest value (leaf) through a deep nested chain. -//! -//! Structure: Root -> Arc> (sync) -> L1 -> L2 -> Arc> (async) -> L3 -> leaf i32 -//! -//! Compares: -//! - Direct lock approach: sync_mutex.lock(), tokio_mutex.lock().await, then access leaf -//! - Keypath approach: LockKp.then().then().then_async().then() chain - -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 tokio::runtime::Runtime; - -#[cfg(all(feature = "tokio", feature = "parking_lot"))] -mod benches { - use super::*; - - // Root -> Arc> - #[derive(Clone)] - pub struct Root { - pub sync_mutex: Arc>, - } - - // L1 -> Level2 (plain) - #[derive(Clone)] - pub struct Level1 { - pub inner: Level2, - } - - // L2 -> Arc> - #[derive(Clone)] - pub struct Level2 { - pub tokio_mutex: Arc>, - } - - // L3 -> leaf i32 - #[derive(Clone)] - pub struct Level3 { - pub leaf: i32, - } - - pub fn make_root() -> Root { - Root { - sync_mutex: Arc::new(Mutex::new(Level1 { - inner: Level2 { - tokio_mutex: Arc::new(tokio::sync::Mutex::new(Level3 { leaf: 42 })), - }, - })), - } - } - - /// Build the deep keypath chain: LockKp(Root->L1).then(L1->L2).then(L2->tokio).then_async(tokio->L3).then(L3->leaf) - #[inline(never)] - 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 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_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< - Arc>, - Arc>, - > = Kp::new(|t: &_| Some(t), |t: &mut _| Some(t)); - let next: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(l), |l: &mut Level3| Some(l)); - 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 step1 = lock_root_to_l1.then(kp_l1_inner); - let step2 = step1.then(kp_l2_tokio); - let step3 = step2.then_async(async_l3); - let deep_chain = step3.then(kp_l3_leaf); - rt.block_on(deep_chain.get(root)) - } - - /// Build the deep keypath chain and mutate leaf - #[inline(never)] - 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 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_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< - Arc>, - Arc>, - > = Kp::new(|t: &_| Some(t), |t: &mut _| Some(t)); - let next: rust_key_paths::KpType = - Kp::new(|l: &Level3| Some(l), |l: &mut Level3| Some(l)); - 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 step1 = lock_root_to_l1.then(kp_l1_inner); - let step2 = step1.then(kp_l2_tokio); - let step3 = step2.then_async(async_l3); - let deep_chain = step3.then(kp_l3_leaf); - rt.block_on(deep_chain.get_mut(root)) - } -} - -#[cfg(all(feature = "tokio", feature = "parking_lot"))] -fn bench_deep_chain_leaf_read(c: &mut Criterion) { - use crate::benches::{Level3, build_and_get, make_root}; - - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("deep_chain_leaf_read"); - - // Keypath approach - group.bench_function("keypath", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let leaf = build_and_get(root_ref, &rt); - black_box(leaf); - }) - }); - - // Direct lock approach - group.bench_function("direct_locks", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - rt.block_on(async { - let tokio_mutex: Arc> = { - let guard = root_ref.sync_mutex.lock().unwrap(); - Arc::clone(&guard.inner.tokio_mutex) - }; - let guard = tokio_mutex.lock().await; - black_box(&guard.leaf); - }) - }) - }); - - group.finish(); -} - -#[cfg(all(feature = "tokio", feature = "parking_lot"))] -fn bench_deep_chain_leaf_write(c: &mut Criterion) { - 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"); - - // Keypath approach - group.bench_function("keypath", |b| { - let mut root = make_root(); - b.iter(|| { - if let Some(l) = build_and_get_mut(black_box(&mut root), &rt) { - *l = 99; - } - }) - }); - - // Direct lock approach - group.bench_function("direct_locks", |b| { - let root = make_root(); - b.iter(|| { - rt.block_on(async { - let tokio_mutex: Arc> = { - let guard = root.sync_mutex.lock().unwrap(); - Arc::clone(&guard.inner.tokio_mutex) - }; - let mut guard = tokio_mutex.lock().await; - guard.leaf = 99; - black_box(&mut guard.leaf); - }) - }) - }); - - group.finish(); -} - -#[cfg(all(feature = "tokio", feature = "parking_lot"))] -criterion_group!( - benches, - bench_deep_chain_leaf_read, - bench_deep_chain_leaf_write, -); - -#[cfg(not(all(feature = "tokio", feature = "parking_lot")))] -fn bench_dummy(c: &mut Criterion) { - let mut group = c.benchmark_group("deep_chain_leaf"); - group.bench_function("skipped_no_tokio_parking_lot", |b| b.iter(|| 0_u64)); - group.finish(); -} - -#[cfg(not(all(feature = "tokio", feature = "parking_lot")))] -criterion_group!(benches, bench_dummy); - -criterion_main!(benches); diff --git a/benches/deep_chain_sync_only.rs b/benches/deep_chain_sync_only.rs deleted file mode 100644 index 8d28f99..0000000 --- a/benches/deep_chain_sync_only.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Benchmark: reading and writing the deepest value (leaf) through sync locks only. -//! -//! Structure: Root -> Arc> -> L1 -> L2 -> Arc> -> L3 -> leaf i32 -//! -//! Compares: -//! - 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::{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> -#[derive(Clone)] -struct Root { - sync_mutex_1: Arc>, -} - -// L1 -> Level2 (plain) -#[derive(Clone)] -struct Level1 { - inner: Level2, -} - -// L2 -> Arc> -#[derive(Clone)] -struct Level2 { - sync_mutex_2: Arc>, -} - -// L3 -> leaf i32 -#[derive(Clone)] -struct Level3 { - leaf: i32, -} - -fn make_root() -> Root { - Root { - sync_mutex_1: Arc::new(Mutex::new(Level1 { - inner: Level2 { - sync_mutex_2: Arc::new(Mutex::new(Level3 { leaf: 42 })), - }, - })), - } -} - -#[inline(never)] -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 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 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 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 step1 = lock_root_to_l1.then(kp_l1_inner); - let step2 = step1.then_lock(lock_l2_to_l3); - let chain = step2.then(kp_l3_leaf); - chain.get(root) -} - -#[inline(never)] -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 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 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 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 step1 = lock_root_to_l1.then(kp_l1_inner); - let step2 = step1.then_lock(lock_l2_to_l3); - let chain = step2.then(kp_l3_leaf); - chain.get_mut(root) -} - -fn bench_deep_chain_sync_read(c: &mut Criterion) { - let mut group = c.benchmark_group("deep_chain_sync_read"); - - group.bench_function("keypath", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let leaf = build_and_get(root_ref); - black_box(leaf); - }) - }); - - group.bench_function("direct_locks", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let guard1 = root_ref.sync_mutex_1.lock().unwrap(); - let guard2 = guard1.inner.sync_mutex_2.lock().unwrap(); - let leaf = &guard2.leaf; - black_box(leaf); - }) - }); - - group.finish(); -} - -fn bench_deep_chain_sync_write(c: &mut Criterion) { - let mut group = c.benchmark_group("deep_chain_sync_write"); - - group.bench_function("keypath", |b| { - let mut root = make_root(); - b.iter(|| { - if let Some(l) = build_and_get_mut(black_box(&mut root)) { - *l = 99; - } - }) - }); - - group.bench_function("direct_locks", |b| { - let mut root = make_root(); - b.iter(|| { - let mut guard1 = root.sync_mutex_1.lock().unwrap(); - let mut guard2 = guard1.inner.sync_mutex_2.lock().unwrap(); - guard2.leaf = 99; - black_box(&mut guard2.leaf); - }) - }); - - group.finish(); -} - -criterion_group!( - benches, - bench_deep_chain_sync_read, - bench_deep_chain_sync_write, -); -criterion_main!(benches); diff --git a/benches/keypath_vs_unwrap.rs b/benches/keypath_vs_unwrap.rs deleted file mode 100644 index 25f9fbd..0000000 --- a/benches/keypath_vs_unwrap.rs +++ /dev/null @@ -1,577 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use key_paths_derive::Kp; -use parking_lot::RwLock; -use std::sync::Arc; - -// Structs renamed for better readability - Level1 is root, Level2, Level3, etc. indicate nesting depth -#[derive(Debug, Kp)] -struct Level1Struct { - level1_field: Option, - level1_field2: Arc>, -} - -#[derive(Debug, Kp)] -struct Level2Struct { - level2_field: Option, -} - -#[derive(Debug, Kp)] -enum Level3Enum { - A(String), - B(Box), -} - -#[derive(Debug, Kp)] -struct Level3Struct { - level3_field: Option, - level3_enum_field: Option, - level3_deep_field: Option, // For 5-level deep nesting without enum -} - -#[derive(Debug, Kp)] -struct Level3EnumStruct { - level3_enum_struct_field: Option, -} - -// Additional structs for 5-level deep nesting without enum -#[derive(Debug, Kp)] -struct Level4Struct { - level4_field: Option, -} - -#[derive(Debug, Kp)] -struct Level5Struct { - level5_field: Option, -} - -impl Level1Struct { - fn new() -> Self { - Self { - level1_field: Some(Level2Struct { - level2_field: Some(Level3Struct { - level3_field: Some(String::from("level 3 value")), - level3_enum_field: Some(Level3Enum::B(Box::new(Level3EnumStruct { - level3_enum_struct_field: Some(String::from("level 3 enum struct field")), - }))), - level3_deep_field: Some(Level4Struct { - level4_field: Some(Level5Struct { - level5_field: Some(String::from("level 5 value")), - }), - }), - }), - }), - level1_field2: Arc::new(RwLock::new(Level2Struct { - level2_field: Some(Level3Struct { - level3_field: Some(String::from("level 3 value")), - level3_enum_field: Some(Level3Enum::B(Box::new(Level3EnumStruct { - level3_enum_struct_field: Some(String::from("level 3 enum struct field")), - }))), - level3_deep_field: Some(Level4Struct { - level4_field: Some(Level5Struct { - level5_field: Some(String::from("level 5 value")), - }), - }), - }), - })), - } - } -} - -// Benchmark: Read access through nested Option chain (3 levels) -fn bench_read_nested_option(c: &mut Criterion) { - let mut group = c.benchmark_group("read_nested_option"); - - let instance = Level1Struct::new(); - let kp = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_field()); - - // Keypath approach: Level1 -> Level2 -> Level3 - group.bench_function("keypath", |b| { - b.iter(|| { - let result = kp.get(black_box(&instance)); - black_box(result) - }) - }); - - // Direct unwrap approach - group.bench_function("direct_unwrap", |b| { - b.iter(|| { - let result = instance - .level1_field - .as_ref() - .and_then(|l2| l2.level2_field.as_ref()) - .and_then(|l3| l3.level3_field.as_ref()); - black_box(result) - }) - }); - - group.finish(); -} - -// Benchmark: Write access through nested Option chain (3 levels) -fn bench_write_nested_option(c: &mut Criterion) { - let mut group = c.benchmark_group("write_nested_option"); - - group.bench_function("keypath", |b| { - let mut instance = Level1Struct::new(); - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_field()); - let result = keypath.get_mut(black_box(&mut instance)); - black_box(result.is_some()) - }) - }); - - // Direct unwrap approach - group.bench_function("direct_unwrap", |b| { - let mut instance = Level1Struct::new(); - b.iter(|| { - let result = instance - .level1_field - .as_mut() - .and_then(|l2| l2.level2_field.as_mut()) - .and_then(|l3| l3.level3_field.as_mut()); - // Use the result without returning the reference - black_box(result.is_some()) - }) - }); - - group.finish(); -} - -// Deep nested read without enum (5 levels deep - matching enum depth) -fn bench_deep_nested_without_enum(c: &mut Criterion) { - let mut group = c.benchmark_group("deep_nested_without_enum"); - - let instance = Level1Struct::new(); - - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_deep_field()) - .then(Level4Struct::level4_field()) - .then(Level5Struct::level5_field()); - - group.bench_function("keypath", |b| { - b.iter(|| { - let result = keypath.get(black_box(&instance)); - black_box(result) - }) - }); - - // Direct unwrap approach - 5 levels deep - group.bench_function("direct_unwrap", |b| { - b.iter(|| { - let result = instance - .level1_field - .as_ref() - .and_then(|l2| l2.level2_field.as_ref()) - .and_then(|l3| l3.level3_deep_field.as_ref()) - .and_then(|l4| l4.level4_field.as_ref()) - .and_then(|l5| l5.level5_field.as_ref()); - black_box(result) - }) - }); - - group.finish(); -} - -// Deep nested read with enum (5 levels deep) -fn bench_deep_nested_with_enum(c: &mut Criterion) { - let mut group = c.benchmark_group("deep_nested_with_enum"); - - let instance = Level1Struct::new(); - - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_enum_field()) - .then(Level3Enum::b()) - .then(Level3EnumStruct::level3_enum_struct_field()); - - group.bench_function("keypath", |b| { - b.iter(|| { - let result = keypath.get(black_box(&instance)); - black_box(result) - }) - }); - - // Direct unwrap approach - group.bench_function("direct_unwrap", |b| { - b.iter(|| { - let result = instance - .level1_field - .as_ref() - .and_then(|l2| l2.level2_field.as_ref()) - .and_then(|l3| l3.level3_enum_field.as_ref()) - .and_then(|e| match e { - Level3Enum::B(ds) => Some(ds), - _ => None, - }) - .and_then(|ds| ds.level3_enum_struct_field.as_ref()); - black_box(result) - }) - }); - - group.finish(); -} -// Benchmark: Write access with enum case path (5 levels deep) -fn bench_write_deep_nested_with_enum(c: &mut Criterion) { - let mut group = c.benchmark_group("write_deep_nested_with_enum"); - - group.bench_function("keypath", |b| { - let mut instance = Level1Struct::new(); - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_enum_field()) - .then(Level3Enum::b()) - .then(Level3EnumStruct::level3_enum_struct_field()); - let result = keypath.get_mut(black_box(&mut instance)); - black_box(result.is_some()) - }) - }); - - // Direct unwrap approach - group.bench_function("direct_unwrap", |b| { - let mut instance = Level1Struct::new(); - b.iter(|| { - let result = instance - .level1_field - .as_mut() - .and_then(|l2| l2.level2_field.as_mut()) - .and_then(|l3| l3.level3_enum_field.as_mut()) - .and_then(|e| match e { - Level3Enum::B(ds) => Some(ds), - _ => None, - }) - .and_then(|ds| ds.level3_enum_struct_field.as_mut()); - // Use the result without returning the reference - black_box(result.is_some()) - }) - }); - - group.finish(); -} - -// Benchmark: Keypath creation overhead -fn bench_keypath_creation(c: &mut Criterion) { - let mut group = c.benchmark_group("keypath_creation"); - - group.bench_function("create_complex_keypath", |b| { - let instance = Level1Struct::new(); - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_enum_field()) - .then(Level3Enum::b()) - .then(Level3EnumStruct::level3_enum_struct_field()); - black_box(keypath.get(black_box(&instance)).is_some()) - }) - }); - - group.finish(); -} - -// Benchmark: Multiple accesses with same keypath (reuse) -fn bench_keypath_reuse(c: &mut Criterion) { - let mut group = c.benchmark_group("keypath_reuse"); - - let mut instances: Vec<_> = (0..100).map(|_| Level1Struct::new()).collect(); - - group.bench_function("keypath_reused", |b| { - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_field()); - let mut sum = 0; - for instance in &mut instances { - if let Some(value) = keypath.get_mut(instance) { - sum += value.len(); - } - } - black_box(sum) - }) - }); - - group.bench_function("direct_unwrap_repeated", |b| { - b.iter(|| { - let mut sum = 0; - for instance in &instances { - if let Some(l2) = instance.level1_field.as_ref() { - if let Some(l3) = l2.level2_field.as_ref() { - if let Some(l3_field) = l3.level3_field.as_ref() { - sum += l3_field.len(); - } - } - } - } - black_box(sum) - }) - }); - - group.finish(); -} - -// Benchmark: 5-level keypath reuse (build keypath once per iter, 100 accesses) -fn bench_keypath_reuse_5_level(c: &mut Criterion) { - let mut group = c.benchmark_group("keypath_reuse_5_level"); - - let mut instances: Vec<_> = (0..100).map(|_| Level1Struct::new()).collect(); - - group.bench_function("keypath_reused_5_level", |b| { - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_deep_field()) - .then(Level4Struct::level4_field()) - .then(Level5Struct::level5_field()); - let mut sum = 0; - for instance in &mut instances { - if let Some(value) = keypath.get_mut(instance) { - sum += value.len(); - } - } - black_box(sum) - }) - }); - - group.bench_function("direct_unwrap_repeated_5_level", |b| { - b.iter(|| { - let mut sum = 0; - for instance in &instances { - if let Some(l2) = instance.level1_field.as_ref() { - if let Some(l3) = l2.level2_field.as_ref() { - if let Some(l4) = l3.level3_deep_field.as_ref() { - if let Some(l5) = l4.level4_field.as_ref() { - if let Some(s) = l5.level5_field.as_ref() { - sum += s.len(); - } - } - } - } - } - } - black_box(sum) - }) - }); - - group.finish(); -} - -// Benchmark: Composition overhead -fn bench_composition_overhead(c: &mut Criterion) { - let mut group = c.benchmark_group("composition_overhead"); - - let mut instance = Level1Struct::new(); - - group.bench_function("pre_composed", |b| { - b.iter(|| { - let pre_composed = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_field()); - let result = pre_composed.get_mut(black_box(&mut instance)); - black_box(result.is_some()) - }) - }); - - // Composed on-the-fly - group.bench_function("composed_on_fly", |b| { - b.iter(|| { - let keypath = Level1Struct::level1_field() - .then(Level2Struct::level2_field()) - .then(Level3Struct::level3_field()); - let result = keypath.get(black_box(&instance)).map(|s| s.len()); - black_box(result) - }) - }); - - group.finish(); -} - -// 10-level deep struct definitions -#[derive(Debug, Clone, Kp)] -struct TenLevel1Struct { - level1_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel2Struct { - level2_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel3Struct { - level3_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel4Struct { - level4_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel5Struct { - level5_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel6Struct { - level6_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel7Struct { - level7_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel8Struct { - level8_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel9Struct { - level9_field: Option, -} - -#[derive(Debug, Clone, Kp)] -struct TenLevel10Struct { - level10_field: Option, -} - -impl TenLevel1Struct { - fn new() -> Self { - Self { - level1_field: Some(TenLevel2Struct { - level2_field: Some(TenLevel3Struct { - level3_field: Some(TenLevel4Struct { - level4_field: Some(TenLevel5Struct { - level5_field: Some(TenLevel6Struct { - level6_field: Some(TenLevel7Struct { - level7_field: Some(TenLevel8Struct { - level8_field: Some(TenLevel9Struct { - level9_field: Some(TenLevel10Struct { - level10_field: Some(String::from("level 10 value")), - }), - }), - }), - }), - }), - }), - }), - }), - }), - } - } -} - -// Benchmark: 10-level deep read and write operations -fn bench_ten_level(c: &mut Criterion) { - let mut group = c.benchmark_group("ten_level"); - - // Read benchmark - let instance = TenLevel1Struct::new(); - group.bench_function("read", |b| { - b.iter(|| { - let read_kp = TenLevel1Struct::level1_field() - .then(TenLevel2Struct::level2_field()) - .then(TenLevel3Struct::level3_field()) - .then(TenLevel4Struct::level4_field()) - .then(TenLevel5Struct::level5_field()) - .then(TenLevel6Struct::level6_field()) - .then(TenLevel7Struct::level7_field()) - .then(TenLevel8Struct::level8_field()) - .then(TenLevel9Struct::level9_field()) - .then(TenLevel10Struct::level10_field()); - let result = read_kp.get(black_box(&instance)); - black_box(result.is_some()) - }) - }); - - // Write benchmark - let mut instance_mut = TenLevel1Struct::new(); - - group.bench_function("write", |b| { - b.iter(|| { - let write_kp = TenLevel1Struct::level1_field() - .then(TenLevel2Struct::level2_field()) - .then(TenLevel3Struct::level3_field()) - .then(TenLevel4Struct::level4_field()) - .then(TenLevel5Struct::level5_field()) - .then(TenLevel6Struct::level6_field()) - .then(TenLevel7Struct::level7_field()) - .then(TenLevel8Struct::level8_field()) - .then(TenLevel9Struct::level9_field()) - .then(TenLevel10Struct::level10_field()); - if let Some(value) = write_kp.get_mut(black_box(&mut instance_mut)) { - *value = String::from("updated value"); - } - black_box(()) - }) - }); - - // Traditional approach for comparison (read) - group.bench_function("read_traditional", |b| { - b.iter(|| { - let result = instance - .level1_field - .as_ref() - .and_then(|l2| l2.level2_field.as_ref()) - .and_then(|l3| l3.level3_field.as_ref()) - .and_then(|l4| l4.level4_field.as_ref()) - .and_then(|l5| l5.level5_field.as_ref()) - .and_then(|l6| l6.level6_field.as_ref()) - .and_then(|l7| l7.level7_field.as_ref()) - .and_then(|l8| l8.level8_field.as_ref()) - .and_then(|l9| l9.level9_field.as_ref()) - .and_then(|l10| l10.level10_field.as_ref()); - black_box(result.is_some()) - }) - }); - - // Traditional approach for comparison (write) - group.bench_function("write_traditional", |b| { - b.iter(|| { - if let Some(l2) = instance_mut.level1_field.as_mut() { - if let Some(l3) = l2.level2_field.as_mut() { - if let Some(l4) = l3.level3_field.as_mut() { - if let Some(l5) = l4.level4_field.as_mut() { - if let Some(l6) = l5.level5_field.as_mut() { - if let Some(l7) = l6.level6_field.as_mut() { - if let Some(l8) = l7.level7_field.as_mut() { - if let Some(l9) = l8.level8_field.as_mut() { - if let Some(l10) = l9.level9_field.as_mut() { - if let Some(value) = l10.level10_field.as_mut() { - *value = String::from("updated value"); - } - } - } - } - } - } - } - } - } - } - black_box(()) - }) - }); - - group.finish(); -} - -criterion_group!( - benches, - bench_read_nested_option, - bench_write_nested_option, - bench_deep_nested_without_enum, - bench_deep_nested_with_enum, - bench_write_deep_nested_with_enum, - bench_keypath_creation, - bench_keypath_reuse, - bench_keypath_reuse_5_level, - bench_composition_overhead, - bench_ten_level -); -criterion_main!(benches); diff --git a/benches/kp_plain_only.rs b/benches/kp_plain_only.rs deleted file mode 100644 index f4f7e7e..0000000 --- a/benches/kp_plain_only.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Benchmark: reading and writing the deepest value (leaf) through a plain Kp chain (no locks). -//! -//! Structure: Root -> L1 -> L2 -> L3 -> leaf i32 -//! -//! Compares: -//! - Keypath approach: Kp.then().then().then() chain -//! - Direct field access: root.l1.inner.leaf - -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use rust_key_paths::Kp; - -// Root -> Level1 -#[derive(Clone)] -struct Root { - l1: Level1, -} - -// L1 -> Level2 -#[derive(Clone)] -struct Level1 { - inner: Level2, -} - -// L2 -> Level3 -#[derive(Clone)] -struct Level2 { - inner: Level3, -} - -// L3 -> leaf i32 -#[derive(Clone)] -struct Level3 { - leaf: i32, -} - -fn make_root() -> Root { - Root { - l1: Level1 { - inner: Level2 { - inner: Level3 { leaf: 42 }, - }, - }, - } -} - -#[inline(never)] -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 step1 = kp_l1.then(kp_l2); - let step2 = step1.then(kp_l3); - let chain = step2.then(kp_leaf); - chain.get(root) -} - -#[inline(never)] -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 step1 = kp_l1.then(kp_l2); - let step2 = step1.then(kp_l3); - let chain = step2.then(kp_leaf); - chain.get_mut(root) -} - -fn bench_kp_plain_read(c: &mut Criterion) { - let mut group = c.benchmark_group("kp_plain_read"); - - // Keypath approach - group.bench_function("keypath", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let leaf = build_and_get(root_ref); - black_box(leaf); - }) - }); - - // Direct field access - group.bench_function("direct", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let leaf = &root_ref.l1.inner.inner.leaf; - black_box(leaf); - }) - }); - - group.finish(); -} - -fn bench_kp_plain_write(c: &mut Criterion) { - let mut group = c.benchmark_group("kp_plain_write"); - - // Keypath approach - group.bench_function("keypath", |b| { - let mut root = make_root(); - b.iter(|| { - if let Some(l) = build_and_get_mut(black_box(&mut root)) { - *l = 99; - } - }) - }); - - // Direct field access - group.bench_function("direct", |b| { - let mut root = make_root(); - b.iter(|| { - root.l1.inner.inner.leaf = 99; - black_box(&mut root.l1.inner.inner.leaf); - }) - }); - - group.finish(); -} - -criterion_group!(benches, bench_kp_plain_read, bench_kp_plain_write); -criterion_main!(benches); diff --git a/benches/run_benchmarks.sh b/benches/run_benchmarks.sh deleted file mode 100755 index 58e4f37..0000000 --- a/benches/run_benchmarks.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Benchmark Performance Report Generator -# Compares KeyPaths vs Direct Unwrap Performance - -set -e - -echo "🔬 KeyPaths Performance Benchmark Report" -echo "==========================================" -echo "" -echo "Running comprehensive benchmarks comparing KeyPaths vs Direct Unwrap..." -echo "" - -# Run benchmarks -cargo bench --bench keypath_vs_unwrap - -echo "" -echo "✅ Benchmarks completed!" -echo "" -echo "📊 Results are available in:" -echo " - target/criterion/keypath_vs_unwrap/" -echo " - HTML reports: target/criterion/keypath_vs_unwrap/*/report/index.html" -echo "" -echo "To view results, open the HTML files in your browser." - diff --git a/benches/rwlock_write_deeply_nested.rs b/benches/rwlock_write_deeply_nested.rs deleted file mode 100644 index 1eb7364..0000000 --- a/benches/rwlock_write_deeply_nested.rs +++ /dev/null @@ -1,478 +0,0 @@ -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use keypaths_proc::Kp; -use parking_lot::RwLock; -use std::sync::Arc; -#[cfg(feature = "tokio")] -use tokio::runtime::Runtime; -#[cfg(feature = "tokio")] -use tokio::sync::RwLock as TokioRwLock; - -// Struct definitions matching the user's example -#[derive(Kp)] -#[Writable] -struct SomeStruct { - f1: Arc>, -} - -#[derive(Kp)] -#[Writable] -struct SomeOtherStruct { - f3: Option, - f4: DeeplyNestedStruct, -} - -#[derive(Kp)] -#[Writable] -struct DeeplyNestedStruct { - f1: Option, - f2: Option, -} - -impl SomeStruct { - fn new() -> Self { - Self { - f1: Arc::new(RwLock::new(SomeOtherStruct { - f3: Some(String::from("value")), - f4: DeeplyNestedStruct { - f1: Some(String::from("value")), - f2: Some(12), - }, - })), - } - } -} - -// Benchmark: Write access through Arc> with deeply nested keypath -fn bench_rwlock_write_deeply_nested_keypath(c: &mut Criterion) { - let mut group = c.benchmark_group("rwlock_write_deeply_nested"); - - // Keypath approach: SomeStruct -> Arc> -> SomeOtherStruct -> DeeplyNestedStruct -> f1 - group.bench_function("keypath", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let keypath = - SomeStruct::f1_fw_at(SomeOtherStruct::f4_w().then(DeeplyNestedStruct::f1_w())); - keypath.get_mut(black_box(&instance), |value| { - *value = Some(String::from("new value")); - }); - black_box(()) - }) - }); - - // Traditional approach: Manual write guard and nested unwraps - group.bench_function("write_guard", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let mut guard = instance.f1.write(); - if let Some(f4) = guard.f4.f1.as_mut() { - *f4 = String::from("new value"); - } - black_box(()) - }) - }); - - // Alternative traditional approach: More explicit nested unwraps - group.bench_function("write_guard_nested", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let mut guard = instance.f1.write(); - if let Some(ref mut f4) = guard.f4.f1 { - *f4 = String::from("new value"); - } - black_box(()) - }) - }); - - group.finish(); -} - -// Benchmark: Write access to f2 (Option) in deeply nested structure -fn bench_rwlock_write_deeply_nested_f2(c: &mut Criterion) { - let mut group = c.benchmark_group("rwlock_write_deeply_nested_f2"); - - // Keypath approach: SomeStruct -> Arc> -> SomeOtherStruct -> DeeplyNestedStruct -> f2 - group.bench_function("keypath", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let keypath = - SomeStruct::f1_fw_at(SomeOtherStruct::f4_w().then(DeeplyNestedStruct::f2_w())); - keypath.get_mut(black_box(&instance), |value| { - *value = Some(42); - }); - black_box(()) - }) - }); - - // Traditional approach - group.bench_function("write_guard", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let mut guard = instance.f1.write(); - if let Some(ref mut f2) = guard.f4.f2 { - *f2 = 42; - } - black_box(()) - }) - }); - - group.finish(); -} - -// Benchmark: Write access to f3 (Option) in SomeOtherStruct -fn bench_rwlock_write_f3(c: &mut Criterion) { - let mut group = c.benchmark_group("rwlock_write_f3"); - - // Keypath approach: SomeStruct -> Arc> -> f3 - group.bench_function("keypath", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let keypath = SomeStruct::f1_fw_at(SomeOtherStruct::f3_w()); - keypath.get_mut(black_box(&instance), |value| { - *value = Some(String::from("updated f3")); - }); - black_box(()) - }) - }); - - // Traditional approach - group.bench_function("write_guard", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let mut guard = instance.f1.write(); - if let Some(ref mut f3) = guard.f3 { - *f3 = String::from("updated f3"); - } - black_box(()) - }) - }); - - group.finish(); -} - -// Benchmark: Multiple sequential writes using keypath vs write guard -fn bench_rwlock_multiple_writes(c: &mut Criterion) { - let mut group = c.benchmark_group("rwlock_multiple_writes"); - - // Keypath approach: Create keypaths in each iteration - group.bench_function("keypath", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let keypath_f3 = SomeStruct::f1_fw_at(SomeOtherStruct::f3_w()); - keypath_f3.get_mut(black_box(&instance), |value| { - *value = Some(String::from("updated f3")); - }); - let keypath_f1 = - SomeStruct::f1_fw_at(SomeOtherStruct::f4_w().then(DeeplyNestedStruct::f1_w())); - keypath_f1.get_mut(black_box(&instance), |value| { - *value = Some(String::from("updated f1")); - }); - let keypath_f2 = - SomeStruct::f1_fw_at(SomeOtherStruct::f4_w().then(DeeplyNestedStruct::f2_w())); - keypath_f2.get_mut(black_box(&instance), |value| { - *value = Some(42); - }); - black_box(()) - }) - }); - - // Traditional approach: Single write guard for all operations - group.bench_function("write_guard_single", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - let mut guard = instance.f1.write(); - if let Some(ref mut f3) = guard.f3 { - *f3 = String::from("updated f3"); - } - if let Some(ref mut f1) = guard.f4.f1 { - *f1 = String::from("updated f1"); - } - if let Some(ref mut f2) = guard.f4.f2 { - *f2 = 42; - } - black_box(()) - }) - }); - - // Traditional approach: Multiple write guards (less efficient) - group.bench_function("write_guard_multiple", |b| { - let instance = SomeStruct::new(); - b.iter(|| { - { - let mut guard = instance.f1.write(); - if let Some(ref mut f3) = guard.f3 { - *f3 = String::from("updated f3"); - } - } - { - let mut guard = instance.f1.write(); - if let Some(ref mut f1) = guard.f4.f1 { - *f1 = String::from("updated f1"); - } - } - { - let mut guard = instance.f1.write(); - if let Some(ref mut f2) = guard.f4.f2 { - *f2 = 42; - } - } - black_box(()) - }) - }); - - group.finish(); -} - -// ========== TOKIO RWLock BENCHMARKS ========== - -#[cfg(feature = "tokio")] -#[derive(Kp)] -#[All] // Generate all methods (readable, writable, owned) -struct TokioSomeStruct { - f1: Arc>, -} - -#[cfg(feature = "tokio")] -#[derive(Kp)] -#[All] // Generate all methods (readable, writable, owned) -struct TokioSomeOtherStruct { - f3: Option, - f4: TokioDeeplyNestedStruct, -} - -#[cfg(feature = "tokio")] -#[derive(Kp)] -#[All] // Generate all methods (readable, writable, owned) -struct TokioDeeplyNestedStruct { - f1: Option, - f2: Option, -} - -#[cfg(feature = "tokio")] -impl TokioSomeStruct { - fn new() -> Self { - Self { - f1: Arc::new(tokio::sync::RwLock::new(TokioSomeOtherStruct { - f3: Some(String::from("value")), - f4: TokioDeeplyNestedStruct { - f1: Some(String::from("value")), - f2: Some(12), - }, - })), - } - } -} - -// Benchmark: Read access through Arc> with deeply nested keypath -#[cfg(feature = "tokio")] -fn bench_tokio_rwlock_read_deeply_nested_keypath(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("tokio_rwlock_read_deeply_nested"); - - // Keypath approach: TokioSomeStruct -> Arc> -> TokioSomeOtherStruct -> TokioDeeplyNestedStruct -> f1 - group.bench_function("keypath", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let keypath = TokioSomeStruct::f1_fr_at( - TokioSomeOtherStruct::f4_r().then(TokioDeeplyNestedStruct::f1_r()), - ); - keypath - .get(black_box(&instance), |value| { - black_box(value); - }) - .await; - }) - }) - }); - - // Traditional approach: Manual read guard and nested access - group.bench_function("read_guard", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let guard = instance.f1.read().await; - let _value = black_box(&guard.f4.f1); - }) - }) - }); - - group.finish(); -} - -// Benchmark: Write access through Arc> with deeply nested keypath -#[cfg(feature = "tokio")] -fn bench_tokio_rwlock_write_deeply_nested_keypath(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("tokio_rwlock_write_deeply_nested"); - - // Keypath approach: TokioSomeStruct -> Arc> -> TokioSomeOtherStruct -> TokioDeeplyNestedStruct -> f1 - group.bench_function("keypath", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let keypath = TokioSomeStruct::f1_fw_at( - TokioSomeOtherStruct::f4_w().then(TokioDeeplyNestedStruct::f1_w()), - ); - keypath - .get_mut(black_box(&instance), |value| { - *value = Some(String::from("new value")); - }) - .await; - }) - }) - }); - - // Traditional approach: Manual write guard and nested unwraps - group.bench_function("write_guard", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let mut guard = instance.f1.write().await; - if let Some(f4) = guard.f4.f1.as_mut() { - *f4 = String::from("new value"); - } - black_box(()) - }) - }) - }); - - group.finish(); -} - -// Benchmark: Write access to f2 (Option) in deeply nested structure with Tokio -#[cfg(feature = "tokio")] -fn bench_tokio_rwlock_write_deeply_nested_f2(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("tokio_rwlock_write_deeply_nested_f2"); - - // Keypath approach - group.bench_function("keypath", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let keypath = TokioSomeStruct::f1_fw_at( - TokioSomeOtherStruct::f4_w().then(TokioDeeplyNestedStruct::f2_w()), - ); - keypath - .get_mut(black_box(&instance), |value| { - *value = Some(42); - }) - .await; - }) - }) - }); - - // Traditional approach - group.bench_function("write_guard", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let mut guard = instance.f1.write().await; - if let Some(ref mut f2) = guard.f4.f2 { - *f2 = 42; - } - black_box(()) - }) - }) - }); - - group.finish(); -} - -// Benchmark: Read access to f3 (Option) in SomeOtherStruct with Tokio -#[cfg(feature = "tokio")] -fn bench_tokio_rwlock_read_f3(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("tokio_rwlock_read_f3"); - - // Keypath approach - group.bench_function("keypath", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let keypath = TokioSomeStruct::f1_fr_at(TokioSomeOtherStruct::f3_r()); - keypath - .get(black_box(&instance), |value| { - black_box(value); - }) - .await; - }) - }) - }); - - // Traditional approach - group.bench_function("read_guard", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let guard = instance.f1.read().await; - let _value = black_box(&guard.f3); - }) - }) - }); - - group.finish(); -} - -// Benchmark: Write access to f3 (Option) in SomeOtherStruct with Tokio -#[cfg(feature = "tokio")] -fn bench_tokio_rwlock_write_f3(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("tokio_rwlock_write_f3"); - - // Keypath approach - group.bench_function("keypath", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let keypath = TokioSomeStruct::f1_fw_at(TokioSomeOtherStruct::f3_w()); - keypath - .get_mut(black_box(&instance), |value| { - *value = Some(String::from("updated f3")); - }) - .await; - }) - }) - }); - - // Traditional approach - group.bench_function("write_guard", |b| { - let instance = TokioSomeStruct::new(); - b.iter(|| { - rt.block_on(async { - let mut guard = instance.f1.write().await; - if let Some(ref mut f3) = guard.f3 { - *f3 = String::from("updated f3"); - } - black_box(()) - }) - }) - }); - - group.finish(); -} - -#[cfg(not(feature = "tokio"))] -criterion_group!( - benches, - bench_rwlock_write_deeply_nested_keypath, - bench_rwlock_write_deeply_nested_f2, - bench_rwlock_write_f3, - bench_rwlock_multiple_writes, -); - -#[cfg(feature = "tokio")] -criterion_group!( - benches, - bench_rwlock_write_deeply_nested_keypath, - bench_rwlock_write_deeply_nested_f2, - bench_rwlock_write_f3, - bench_rwlock_multiple_writes, - bench_tokio_rwlock_read_deeply_nested_keypath, - bench_tokio_rwlock_write_deeply_nested_keypath, - bench_tokio_rwlock_write_deeply_nested_f2, - bench_tokio_rwlock_read_f3, - bench_tokio_rwlock_write_f3, -); - -criterion_main!(benches); diff --git a/benches/scale_par_bench.rs b/benches/scale_par_bench.rs deleted file mode 100644 index 77ef842..0000000 --- a/benches/scale_par_bench.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Benchmark: scale_par keypath-based parallel vs sequential. -//! -//! Compares: -//! - Buffer scaling: sequential (nested for_each) vs keypath par_scale_buffers -//! - 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::{BatchSize, Criterion, black_box, criterion_group, criterion_main}; -use key_paths_iter::query_par::ParallelCollectionKeyPath; -use key_paths_iter::scale_par::{ - 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 { - GpuComputePipeline { - cpu_state: ComputeState { - buffers: (0..num_buffers) - .map(|_| GpuBuffer { - data: vec![1.0_f32; buffer_len], - size: buffer_len, - }) - .collect(), - kernels: vec![], - results: vec![], - metadata: Default::default(), - }, - gpu_buffer_ids: (0..num_buffers as u32).collect(), - reduction_net: InteractionNet { - nodes: (0..num_nodes) - .map(|i| { - let kind = match i % 4 { - 0 => NodeKind::Era, - 1 => NodeKind::Con, - 2 => NodeKind::Dup, - _ => NodeKind::Ref, - }; - 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(), - }, - } -} - -fn sequential_scale_buffers(pipeline: &mut GpuComputePipeline, scale: f32) { - for buf in &mut pipeline.cpu_state.buffers { - for x in &mut buf.data { - *x *= scale; - } - } -} - -fn sequential_validate_buffers_non_empty(pipeline: &GpuComputePipeline) -> bool { - 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() -} - -fn bench_buffer_scale(c: &mut Criterion) { - let mut group = c.benchmark_group("scale_par_buffer_scale"); - 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, - ); - }, - ); - - 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.finish(); -} - -fn bench_validation(c: &mut Criterion) { - let mut group = c.benchmark_group("scale_par_validation"); - group.sample_size(50); - - for (num_buffers, buffer_len) in [(500, 500), (2000, 500)] { - let pipeline = make_pipeline(num_buffers, buffer_len, 5000, 10000); - - group.bench_function( - format!("sequential_all_non_empty_{}buf", num_buffers), - |b| b.iter(|| sequential_validate_buffers_non_empty(black_box(&pipeline))), - ); - - 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_all_non_empty_{}buf", num_buffers), - |b| { - b.iter(|| { - par_validate_buffers_non_empty(black_box(&buffers_kp), black_box(&pipeline)) - }) - }, - ); - } - - group.finish(); -} - -fn bench_count_by(c: &mut Criterion) { - let mut group = c.benchmark_group("scale_par_count_by"); - group.sample_size(50); - - for num_nodes in [5_000_usize, 50_000, 100_000] { - let pipeline = make_pipeline(100, 100, num_nodes, num_nodes * 2); - - group.bench_function(format!("sequential_count_era_{}nodes", num_nodes), |b| { - b.iter(|| sequential_count_nodes_era(black_box(&pipeline))); - }); - - let nodes_kp: KpType<'static, GpuComputePipeline, Vec> = Kp::new( - |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.finish(); -} - -criterion_group!( - scale_par_benches, - bench_buffer_scale, - bench_validation, - bench_count_by, -); -criterion_main!(scale_par_benches); diff --git a/benches/ten_level_arc_rwlock.rs b/benches/ten_level_arc_rwlock.rs deleted file mode 100644 index e056089..0000000 --- a/benches/ten_level_arc_rwlock.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Benchmark: 10-level deep Arc> nesting. -//! -//! Compares: -//! - **Static keypath**: LockKp chain built once, reused (pre-built) -//! - **Dynamic keypath**: LockKp chain built each iteration -//! - **Direct lock acquire**: Manual .read() through 10 levels - -#![cfg(feature = "parking_lot")] - -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use key_paths_derive::Kp; -use std::sync::Arc; - -use parking_lot::RwLock; - -// 10-level deep: L0 -> Arc> -> L1 -> ... -> L10 { leaf: i32 } -#[derive(Clone, Kp)] -struct L0 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L1 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L2 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L3 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L4 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L5 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L6 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L7 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L8 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L9 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L10 { - leaf: f64, -} - -fn make_root() -> L0 { - let leaf = L10 { leaf: 42.0 }; - let l9 = L9 { - inner: Arc::new(RwLock::new(leaf)), - }; - let l8 = L8 { - inner: Arc::new(RwLock::new(l9)), - }; - let l7 = L7 { - inner: Arc::new(RwLock::new(l8)), - }; - let l6 = L6 { - inner: Arc::new(RwLock::new(l7)), - }; - let l5 = L5 { - inner: Arc::new(RwLock::new(l6)), - }; - let l4 = L4 { - inner: Arc::new(RwLock::new(l5)), - }; - let l3 = L3 { - inner: Arc::new(RwLock::new(l4)), - }; - let l2 = L2 { - inner: Arc::new(RwLock::new(l3)), - }; - let l1 = L1 { - inner: Arc::new(RwLock::new(l2)), - }; - L0 { - inner: Arc::new(RwLock::new(l1)), - } -} - -/// Build the 10-level LockKp chain (read path) -fn build_read_chain() -> impl Fn(&L0) -> Option<&f64> { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - - move |root: &L0| chain.get(root) -} - -/// Build and return the chain (for static reuse - caller stores it) -#[inline(never)] -fn build_chain_once() -> impl Fn(&L0) -> Option<&f64> { - build_read_chain() -} - -fn bench_ten_level_read(c: &mut Criterion) { - let mut group = c.benchmark_group("ten_level_arc_rwlock_read"); - - // Static keypath: build chain ONCE, reuse - group.bench_function("keypath_static", |b| { - let chain = build_chain_once(); - let root = make_root(); - b.iter(|| { - let result = chain(black_box(&root)); - black_box(result) - }) - }); - - // Dynamic keypath: build chain each iteration - group.bench_function("keypath_dynamic", |b| { - let root = make_root(); - b.iter(|| { - let chain = build_read_chain(); - let result = chain(black_box(&root)); - black_box(result) - }) - }); - - // Direct lock acquire: manual .read() through 10 levels (guards kept in scope) - group.bench_function("direct_lock", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let g1 = root_ref.inner.read(); - let g2 = g1.inner.read(); - let g3 = g2.inner.read(); - let g4 = g3.inner.read(); - let g5 = g4.inner.read(); - let g6 = g5.inner.read(); - let g7 = g6.inner.read(); - let g8 = g7.inner.read(); - let g9 = g8.inner.read(); - let g10 = g9.inner.read(); - black_box(g10.leaf) - }) - }); - - group.finish(); -} - -fn bench_ten_level_incr(c: &mut Criterion) { - let mut group = c.benchmark_group("ten_level_arc_rwlock_incr"); - - group.bench_function("keypath_static", |b| { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - let mut root = make_root(); - b.iter(|| { - let _ = chain.set(black_box(&mut root), |v| *v += 0.25); - }) - }); - - group.bench_function("keypath_dynamic", |b| { - let mut root = make_root(); - b.iter(|| { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - let _ = chain.set(black_box(&mut root), |v| *v += 0.25); - }) - }); - - group.bench_function("direct_lock", |b| { - let mut root = make_root(); - b.iter(|| { - let root_ref = black_box(&mut root); - let mut g1 = root_ref.inner.write(); - let mut g2 = g1.inner.write(); - let mut g3 = g2.inner.write(); - let mut g4 = g3.inner.write(); - let mut g5 = g4.inner.write(); - let mut g6 = g5.inner.write(); - let mut g7 = g6.inner.write(); - let mut g8 = g7.inner.write(); - let mut g9 = g8.inner.write(); - let mut g10 = g9.inner.write(); - g10.leaf += 0.25; - }) - }); - - group.finish(); -} - -criterion_group! { - benches, - bench_ten_level_read, - bench_ten_level_incr, -} -criterion_main!(benches); diff --git a/benches/ten_level_std_rwlock.rs b/benches/ten_level_std_rwlock.rs deleted file mode 100644 index a52ad45..0000000 --- a/benches/ten_level_std_rwlock.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Benchmark: 10-level deep Arc> nesting. -//! -//! Compares: -//! - **Static keypath**: LockKp chain built once, reused (pre-built) -//! - **Dynamic keypath**: LockKp chain built each iteration -//! - **Direct lock acquire**: Manual .read() through 10 levels - -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use key_paths_derive::Kp; -use std::sync::{Arc, RwLock}; - -// 10-level deep: L0 -> Arc> -> L1 -> ... -> L10 { leaf: i32 } -// Use full path std::sync::RwLock so derive emits StdArcRwLock (inner_lock) not parking_lot -#[derive(Clone, Kp)] -struct L0 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L1 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L2 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L3 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L4 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L5 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L6 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L7 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L8 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L9 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L10 { - leaf: f64, -} - -fn make_root() -> L0 { - let leaf = L10 { leaf: 42.0 }; - let l9 = L9 { - inner: Arc::new(RwLock::new(leaf)), - }; - let l8 = L8 { - inner: Arc::new(RwLock::new(l9)), - }; - let l7 = L7 { - inner: Arc::new(RwLock::new(l8)), - }; - let l6 = L6 { - inner: Arc::new(RwLock::new(l7)), - }; - let l5 = L5 { - inner: Arc::new(RwLock::new(l6)), - }; - let l4 = L4 { - inner: Arc::new(RwLock::new(l5)), - }; - let l3 = L3 { - inner: Arc::new(RwLock::new(l4)), - }; - let l2 = L2 { - inner: Arc::new(RwLock::new(l3)), - }; - let l1 = L1 { - inner: Arc::new(RwLock::new(l2)), - }; - L0 { - inner: Arc::new(RwLock::new(l1)), - } -} - -fn build_read_chain() -> impl Fn(&L0) -> Option<&f64> { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - - move |root: &L0| chain.get(root) -} - -#[inline(never)] -fn build_chain_once() -> impl Fn(&L0) -> Option<&f64> { - build_read_chain() -} - -fn bench_ten_level_read(c: &mut Criterion) { - let mut group = c.benchmark_group("ten_level_std_rwlock_read"); - - group.bench_function("keypath_static", |b| { - let chain = build_chain_once(); - let root = make_root(); - b.iter(|| { - let result = chain(black_box(&root)); - black_box(result) - }) - }); - - group.bench_function("keypath_dynamic", |b| { - let root = make_root(); - b.iter(|| { - let chain = build_read_chain(); - let result = chain(black_box(&root)); - black_box(result) - }) - }); - - group.bench_function("direct_lock", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let g1 = root_ref.inner.read().unwrap(); - let g2 = g1.inner.read().unwrap(); - let g3 = g2.inner.read().unwrap(); - let g4 = g3.inner.read().unwrap(); - let g5 = g4.inner.read().unwrap(); - let g6 = g5.inner.read().unwrap(); - let g7 = g6.inner.read().unwrap(); - let g8 = g7.inner.read().unwrap(); - let g9 = g8.inner.read().unwrap(); - let g10 = g9.inner.read().unwrap(); - black_box(g10.leaf) - }) - }); - - group.finish(); -} - -fn bench_ten_level_incr(c: &mut Criterion) { - let mut group = c.benchmark_group("ten_level_std_rwlock_incr"); - - group.bench_function("keypath_static", |b| { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - let mut root = make_root(); - b.iter(|| { - let _ = chain.set(black_box(&mut root), |v| *v += 0.25); - }) - }); - - group.bench_function("keypath_dynamic", |b| { - let mut root = make_root(); - b.iter(|| { - let chain = L0::inner_lock() - .then_lock(L1::inner_lock()) - .then_lock(L2::inner_lock()) - .then_lock(L3::inner_lock()) - .then_lock(L4::inner_lock()) - .then_lock(L5::inner_lock()) - .then_lock(L6::inner_lock()) - .then_lock(L7::inner_lock()) - .then_lock(L8::inner_lock()) - .then_lock(L9::inner_lock()) - .then(L10::leaf()); - let _ = chain.set(black_box(&mut root), |v| *v += 0.25); - }) - }); - - group.bench_function("direct_lock", |b| { - let mut root = make_root(); - b.iter(|| { - let root_ref = black_box(&mut root); - let mut g1 = root_ref.inner.write().unwrap(); - let mut g2 = g1.inner.write().unwrap(); - let mut g3 = g2.inner.write().unwrap(); - let mut g4 = g3.inner.write().unwrap(); - let mut g5 = g4.inner.write().unwrap(); - let mut g6 = g5.inner.write().unwrap(); - let mut g7 = g6.inner.write().unwrap(); - let mut g8 = g7.inner.write().unwrap(); - let mut g9 = g8.inner.write().unwrap(); - let mut g10 = g9.inner.write().unwrap(); - g10.leaf += 0.25; - }) - }); - - group.finish(); -} - -criterion_group!(benches, bench_ten_level_read, bench_ten_level_incr); -criterion_main!(benches); diff --git a/benches/ten_level_tokio_rwlock.rs b/benches/ten_level_tokio_rwlock.rs deleted file mode 100644 index 88651a1..0000000 --- a/benches/ten_level_tokio_rwlock.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! Benchmark: 10-level deep Arc> nesting. -//! -//! Compares: -//! - **Static keypath**: AsyncLockKp chain built once, reused -//! - **Dynamic keypath**: Chain built each iteration -//! - **Direct lock acquire**: Manual .read().await through 10 levels - -#![cfg(feature = "tokio")] - -use criterion::{Criterion, black_box, criterion_group, criterion_main}; -use key_paths_derive::Kp; -use std::sync::Arc; -use tokio::runtime::Runtime; -use tokio::sync::RwLock; - -// 10-level deep: L0 -> Arc> -> L1 -> ... -> L10 { leaf: i32 } -// Use full path so derive emits TokioArcRwLock (inner_async) -#[derive(Clone, Kp)] -struct L0 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L1 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L2 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L3 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L4 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L5 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L6 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L7 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L8 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L9 { - inner: Arc>, -} -#[derive(Clone, Kp)] -struct L10 { - leaf: f64, -} - -fn make_root() -> L0 { - let leaf = L10 { leaf: 42.0 }; - let l9 = L9 { - inner: Arc::new(RwLock::new(leaf)), - }; - let l8 = L8 { - inner: Arc::new(RwLock::new(l9)), - }; - let l7 = L7 { - inner: Arc::new(RwLock::new(l8)), - }; - let l6 = L6 { - inner: Arc::new(RwLock::new(l7)), - }; - let l5 = L5 { - inner: Arc::new(RwLock::new(l6)), - }; - let l4 = L4 { - inner: Arc::new(RwLock::new(l5)), - }; - let l3 = L3 { - inner: Arc::new(RwLock::new(l4)), - }; - let l2 = L2 { - inner: Arc::new(RwLock::new(l3)), - }; - let l1 = L1 { - inner: Arc::new(RwLock::new(l2)), - }; - L0 { - inner: Arc::new(RwLock::new(l1)), - } -} - -macro_rules! make_chain { - () => { - L0::inner_async() - .then_async(L1::inner_async()) - .then_async(L2::inner_async()) - .then_async(L3::inner_async()) - .then_async(L4::inner_async()) - .then_async(L5::inner_async()) - .then_async(L6::inner_async()) - .then_async(L7::inner_async()) - .then_async(L8::inner_async()) - .then_async(L9::inner_async()) - .then(L10::leaf()) - }; -} - -fn bench_ten_level_read(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("ten_level_tokio_rwlock_read"); - - group.bench_function("keypath_static", |b| { - let chain = make_chain!(); - let root = make_root(); - b.iter(|| { - let result = rt.block_on(chain.get(black_box(&root))); - black_box(result) - }) - }); - - group.bench_function("keypath_dynamic", |b| { - let root = make_root(); - b.iter(|| { - let chain = make_chain!(); - let result = rt.block_on(chain.get(black_box(&root))); - black_box(result) - }) - }); - - group.bench_function("direct_lock", |b| { - let root = make_root(); - b.iter(|| { - let root_ref = black_box(&root); - let result = rt.block_on(async { - let g1 = root_ref.inner.read().await; - let g2 = g1.inner.read().await; - let g3 = g2.inner.read().await; - let g4 = g3.inner.read().await; - let g5 = g4.inner.read().await; - let g6 = g5.inner.read().await; - let g7 = g6.inner.read().await; - let g8 = g7.inner.read().await; - let g9 = g8.inner.read().await; - let g10 = g9.inner.read().await; - g10.leaf - }); - black_box(result) - }) - }); - - group.finish(); -} - -fn bench_ten_level_incr(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("ten_level_tokio_rwlock_incr"); - - group.bench_function("keypath_static", |b| { - let chain = make_chain!(); - let mut root = make_root(); - b.iter(|| { - rt.block_on(async { - if let Some(v) = chain.get_mut(black_box(&mut root)).await { - *v += 0.25; - } - }); - }) - }); - - group.bench_function("keypath_dynamic", |b| { - let mut root = make_root(); - b.iter(|| { - let chain = make_chain!(); - rt.block_on(async { - if let Some(v) = chain.get_mut(black_box(&mut root)).await { - *v += 0.25; - } - }); - }) - }); - - group.bench_function("direct_lock", |b| { - let mut root = make_root(); - b.iter(|| { - rt.block_on(async { - let root_ref = black_box(&mut root); - let mut g1 = root_ref.inner.write().await; - let mut g2 = g1.inner.write().await; - let mut g3 = g2.inner.write().await; - let mut g4 = g3.inner.write().await; - let mut g5 = g4.inner.write().await; - let mut g6 = g5.inner.write().await; - let mut g7 = g6.inner.write().await; - let mut g8 = g7.inner.write().await; - let mut g9 = g8.inner.write().await; - let mut g10 = g9.inner.write().await; - g10.leaf += 0.25; - }); - }) - }); - - group.finish(); -} - -criterion_group!(benches, bench_ten_level_read, bench_ten_level_incr); -criterion_main!(benches); diff --git a/examples/basics.rs b/examples/basics.rs index 2c1944e..1276088 100644 --- a/examples/basics.rs +++ b/examples/basics.rs @@ -2,8 +2,10 @@ //! //! Run with: `cargo run --example basics` +use std::process::Output; + use key_paths_derive::Kp; -use rust_key_paths::{Kp, KpDynamic, KpType}; +use rust_key_paths::{CoercionTrait, KpDynamic, KpTrait}; pub struct Service { rect_to_width_kp: KpDynamic, @@ -29,6 +31,30 @@ struct Rectangle { name: String, } +impl Rectangle { + // fn kp() -> KpType<'static, Rectangle, String> { + // KpType::new( + // |root| { + // let x = root.name.borrow(); + // let y = &*x as *const String; + // Some(unsafe { &*y })} + // ,|root| { + // let mut x = root.name.borrow_mut(); + // let y = &mut *x as *mut String; + // Some(unsafe {&mut *y}) + // } + // ) + // } + + + // fn kp() -> KpType<'static, Rectangle, std::cell::Ref<'static, String>> { + // KpType::new( + // |root| { Some(&'static root.name.borrow()) } + // ,|_| { None } + // ) + // } + +} // Standalone fn pointers for keypath (reference: lib.rs identity_typed / Kp with fn types) impl Rectangle { @@ -57,7 +83,7 @@ fn main() { // Read: compose keypaths with then() { let width_path = Rectangle::size().then(Size::width()); - if let Some(w) = width_path.get(&rect) { + if let Some(w) = (width_path.get)(&rect) { println!("Width: {}", w); } println!("Width (direct): {:?}", width_path.get(&rect)); @@ -70,5 +96,28 @@ fn main() { *w += 50; } } + + // let kp = Rectangle::size().then(Size::width()).get; + // let kp = |root: &mut Rectangle| {(Rectangle::size().set)(root)}; + // let x:fn() = || {}; + + // let x: fn(&Rectangle) -> Option<&Size> = Rectangle::size().get; + // let y = that_takes(x); + + // let x: fn() = || {}; + + // let x = Rectangle::kp().get(todo!()); + // let x = Rectangle::kp().get_mut(todo!()); println!("Updated rectangle: {:?}", rect); } + + +fn that_takes(f: fn(&Rectangle) -> Option<&Size>) -> for<'a> fn(&'a Rectangle) -> String { + |_root| { "working".to_string() } +} + + +// fn +// impl Fn +// Fn, FnMut, FnOnce +// Box \ No newline at end of file diff --git a/examples/complex_macros.rs b/examples/complex_macros.rs deleted file mode 100644 index 7f89d52..0000000 --- a/examples/complex_macros.rs +++ /dev/null @@ -1,174 +0,0 @@ -use keypaths_proc::{Casepaths, Kp}; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Kp)] -#[All] -struct Profile { - display_name: String, - age: u32, -} - -#[derive(Debug, Kp)] -#[All] -struct User { - id: u64, - profile: Option, - tags: Vec, -} - -#[derive(Debug, Kp)] -#[All] -struct DbConfig(u16, String); // (port, url) - -#[derive(Debug, Kp)] -#[All] -struct Settings { - theme: String, - db: Option, -} - -#[derive(Debug, Casepaths)] -#[All] -enum Connection { - Disconnected, - Connecting(u32), - Connected(String), -} - -#[derive(Debug, Casepaths)] -#[All] -enum Status { - Active(User), - Inactive, - Pending(u32), -} - -#[derive(Debug, Kp)] -#[All] -struct App { - users: Vec, - settings: Option, - connection: Connection, - name: String, -} - -fn main() { - let mut app = App { - users: vec![ - User { - id: 1, - profile: Some(Profile { - display_name: "Ada".into(), - age: 31, - }), - tags: vec!["admin".into(), "founder".into()], - }, - User { - id: 2, - profile: None, - tags: vec!["guest".into()], - }, - ], - settings: Some(Settings { - theme: "dark".into(), - db: Some(DbConfig(5432, "postgres://localhost".into())), - }), - connection: Connection::Connecting(42), - name: "MegaApp".into(), - }; - - // 1) Read a nested optional field via failable readable compose - let first_user_profile_name = App::users_r() - .to_optional() - .then(OptionalKeyPath::new(|v: &Vec| v.first())) - .then(User::profile_fr()) - .then(Profile::display_name_r().to_optional()); - println!( - "first_user_profile_name = {:?}", - first_user_profile_name.get(&app) - ); - - // 2) Mutate nested Option chain via failable writable - let settings_fw = App::settings_fw(); - let db_fw = Settings::db_fw(); - let db_port_w = DbConfig::f0_w(); - if let Some(settings) = settings_fw.get_mut(&mut app) { - if let Some(db) = db_fw.get_mut(settings) { - let port = db_port_w.get_mut(db); - *port += 1; - } - } - println!( - "db after bump = {:?}", - app.settings.as_ref().and_then(|s| s.db.as_ref()) - ); - - // 3) Compose writable + enum case (prism) to mutate only when connected - app.connection = Connection::Connected("10.0.0.1".into()); - let connected_case = Connection::connected_w(); - // compose requires a keypath from App -> Connection first - let app_connection_w = App::connection_w().to_optional(); - let app_connected_ip = app_connection_w.then(connected_case); - if let Some(ip) = app_connected_ip.get_mut(&mut app) { - ip.push_str(":8443"); - } - println!("app.connection = {:?}", app.connection); - - // 4) Enum readable case path for state without payload - app.connection = Connection::Disconnected; - let disc = Connection::disconnected_fr(); - println!("is disconnected? {:?}", disc.get(&app.connection).is_some()); - - // 5) Iterate immutably and mutably via derived vec keypaths - let users_r = App::users_r(); - if let Some(mut iter) = users_r.iter::(&app) { - if let Some(u0) = iter.next() { - println!("first user id = {}", u0.id); - } - } - let users_w = App::users_w(); - if let Some(iter) = users_w.iter_mut::(&mut app) { - for u in iter { - u.tags.push("seen".into()); - } - } - println!("users after tag = {:?}", app.users); - - // 6) Compose across many levels: first user -> profile -> age (if present) and increment - let first_user_fr = OptionalKeyPath::new(|v: &Vec| v.first()); - let profile_fw = User::profile_fw(); - let age_w = Profile::age_w(); - if let Some(u0) = first_user_fr.get(&app.users) { - // borrow helper - let mut app_ref = &mut app.users[0]; - if let Some(p) = profile_fw.get_mut(&mut app_ref) { - let age = age_w.get_mut(p); - *age += 1; - } - } - println!("first user after bday = {:?}", app.users.first()); - - // 7) Embed: build a Connected from payload - let connected_r = Connection::connected_r(); - // Use EnumKeyPath for embedding - let connected_enum = Connection::connected_enum(); - let new_conn = connected_enum.embed("192.168.0.1".to_string()); - println!("embedded = {:?}", new_conn); - - // 8) Additional enum with casepaths: Status - let mut st = Status::Active(User { - id: 99, - profile: None, - tags: vec![], - }); - let st_active = Status::active_r(); - let st_active_name = st_active.then(User::id_r().to_optional()); - println!("status active user id = {:?}", st_active_name.get(&st)); - - let st_pending = Status::pending_w(); - st = Status::Pending(5); - if let Some(v) = st_pending.get_mut(&mut st) { - *v += 1; - } - println!("status after pending increment = {:?}", st); -} diff --git a/examples/compose.rs b/examples/compose.rs deleted file mode 100644 index fadc6d5..0000000 --- a/examples/compose.rs +++ /dev/null @@ -1,56 +0,0 @@ -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug)] -struct Engine { - horsepower: u32, -} - -#[derive(Debug)] -struct Car { - engine: Option, -} - -#[derive(Debug)] -struct Garage { - car: Option, -} - -#[derive(Debug)] -struct City { - garage: Option, -} - -fn main() { - let city = City { - garage: Some(Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 250 }), - }), - }), - }; - - let city_hp2 = OptionalKeyPath::new(|c: &City| { - c.garage - .as_ref() - .and_then(|g| g.car.as_ref()) - .and_then(|car| car.engine.as_ref()) - .and_then(|e| Some(&e.horsepower)) // ✅ removed the extra Some(...) - }); - - println!("Horsepower = {:?}", city_hp2.get(&city)); - - // compose example ---- - // compose keypath together - - let city_garage = OptionalKeyPath::new(|c: &City| c.garage.as_ref()); - let garage_car = OptionalKeyPath::new(|g: &Garage| g.car.as_ref()); - let car_engine = OptionalKeyPath::new(|c: &Car| c.engine.as_ref()); - let engine_hp = OptionalKeyPath::new(|e: &Engine| Some(&e.horsepower)); - - let city_hp = city_garage - .then(garage_car) - .then(car_engine) - .then(engine_hp); - - println!("Horsepower = {:?}", city_hp.get(&city)); -} diff --git a/examples/compose_macros.rs b/examples/compose_macros.rs deleted file mode 100644 index eb63ef2..0000000 --- a/examples/compose_macros.rs +++ /dev/null @@ -1,69 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -#[All] -struct Engine { - horsepower: u32, -} - -#[derive(Debug, Kp)] -#[All] -struct Car { - engine: Option, -} - -#[derive(Debug, Kp)] -#[All] -struct Garage { - car: Option, -} - -#[derive(Debug, Kp)] -#[All] -struct City { - garage: Option, -} - -fn main() { - let city = City { - garage: Some(Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 250 }), - }), - }), - }; - - // Compose using derive-generated failable readable methods - let city_hp = City::garage_fr() - .then(Garage::car_fr()) - .then(Car::engine_fr()) - .then(Engine::horsepower_fr()); - - println!("Horsepower = {:?}", city_hp.get(&city)); - - // Demonstrate writable/failable-writable compose - let mut city2 = City { - garage: Some(Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 100 }), - }), - }), - }; - - let garage_fw = City::garage_fw(); - let car_fw = Garage::car_fw(); - let engine_fw = Car::engine_fw(); - let hp_fw = Engine::horsepower_fw(); - - if let Some(garage) = garage_fw.get_mut(&mut city2) { - if let Some(car) = car_fw.get_mut(garage) { - if let Some(engine) = engine_fw.get_mut(car) { - if let Some(hp) = hp_fw.get_mut(engine) { - *hp += 23; - } - } - } - } - - println!("Updated city2 = {:?}", city2); -} diff --git a/examples/container_adapter_test.rs b/examples/container_adapter_test.rs deleted file mode 100644 index 0a8593a..0000000 --- a/examples/container_adapter_test.rs +++ /dev/null @@ -1,372 +0,0 @@ -// Comprehensive test suite for container adapters -// Run with: cargo run --example container_adapter_test - -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use std::rc::Rc; -use std::sync::Arc; - -#[derive(Debug, Clone)] -struct TestStruct { - name: String, - value: u32, - optional: Option, -} - -fn main() { - println!("=== Container Adapter Test Suite ===\n"); - - let test_data = TestStruct { - name: "Test".to_string(), - value: 42, - optional: Some("Optional Value".to_string()), - }; - - // Create keypaths - let name_path = KeyPath::new(|s: &TestStruct| &s.name); - let name_path_w = WritableKeyPath::new(|s: &mut TestStruct| &mut s.name); - let value_path = KeyPath::new(|s: &TestStruct| &s.value); - let value_path_w = WritableKeyPath::new(|s: &mut TestStruct| &mut s.value); - let optional_path = OptionalKeyPath::new(|s: &TestStruct| s.optional.as_ref()); - let optional_path_w = WritableOptionalKeyPath::new(|s: &mut TestStruct| s.optional.as_mut()); - - // ===== Test 1: Arc Readable ===== - println!("--- Test 1: Arc with Readable KeyPath ---"); - let arc_data = Arc::new(test_data.clone()); - let name_path_arc = name_path.clone().for_arc_root(); - - if let Some(name) = name_path_arc.get(&arc_data) { - println!(" Arc name: {}", name); - assert_eq!(name, "Test", "Arc readable should return correct value"); - } - println!("✓ Test 1 passed\n"); - - // ===== Test 2: Arc with Failable Readable ===== - println!("--- Test 2: Arc with Failable Readable KeyPath ---"); - let optional_path_arc = optional_path.clone().for_arc_root(); - - if let Some(optional_val) = optional_path_arc.get(&arc_data) { - println!(" Arc optional: {}", optional_val); - assert_eq!( - optional_val, "Optional Value", - "Arc failable readable should return correct value" - ); - } - println!("✓ Test 2 passed\n"); - - // ===== Test 3: Arc is Immutable (Note: .for_arc() with writable paths will panic) ===== - println!("--- Test 3: Arc with Writable KeyPath ---"); - println!(" Note: Calling .for_arc() on writable keypaths will panic"); - println!(" Arc is immutable, so only readable keypaths are supported"); - println!(" Skipping panic test (can't catch with catch_unwind)"); - println!("✓ Test 3 passed (documented behavior)\n"); - - // ===== Test 4: Box Readable ===== - println!("--- Test 4: Box with Readable KeyPath ---"); - let box_data = Box::new(test_data.clone()); - let name_path_box = name_path.clone().for_box_root(); - - if let Some(name) = name_path_box.get(&box_data) { - println!(" Box name: {}", name); - assert_eq!(name, "Test", "Box readable should return correct value"); - } - println!("✓ Test 4 passed\n"); - - // ===== Test 5: Box Writable ===== - println!("--- Test 5: Box with Writable KeyPath ---"); - let mut box_data_mut = Box::new(test_data.clone()); - let name_path_box_w = name_path_w.clone().for_box_root(); - - let name = name_path_box_w.get_mut(&mut box_data_mut); - { - println!(" Original Box name: {}", name); - *name = "Modified".to_string(); - println!(" Modified Box name: {}", name); - assert_eq!(name, "Modified", "Box writable should allow modification"); - } - println!("✓ Test 5 passed\n"); - - // ===== Test 6: Box Failable Writable ===== - println!("--- Test 6: Box with Failable Writable KeyPath ---"); - let mut box_data_opt = Box::new(test_data.clone()); - let optional_path_box_w = optional_path_w.clone().for_box_root(); - - if let Some(opt_val) = optional_path_box_w.get_mut(&mut box_data_opt) { - println!(" Original optional: {}", opt_val); - *opt_val = "New Value".to_string(); - println!(" Modified optional: {}", opt_val); - assert_eq!(opt_val, "New Value"); - } - println!("✓ Test 6 passed\n"); - - // ===== Test 7: Rc Readable ===== - println!("--- Test 7: Rc with Readable KeyPath ---"); - let rc_data = Rc::new(test_data.clone()); - let value_path_rc = value_path.clone().for_rc_root(); - - if let Some(value) = value_path_rc.get(&rc_data) { - println!(" Rc value: {}", value); - assert_eq!(*value, 42, "Rc readable should return correct value"); - } - println!("✓ Test 7 passed\n"); - - // ===== Test 8: Rc is Immutable (Note: .for_rc() with writable paths will panic) ===== - println!("--- Test 8: Rc with Writable KeyPath ---"); - println!(" Note: Calling .for_rc() on writable keypaths will panic"); - println!(" Rc is immutable, so only readable keypaths are supported"); - println!(" Skipping panic test (can't catch with catch_unwind)"); - println!("✓ Test 8 passed (documented behavior)\n"); - - // ===== Test 9: Vec> Collection ===== - println!("--- Test 9: Vec> Collection ---"); - let collection: Vec> = vec![ - Arc::new(TestStruct { - name: "Item 1".to_string(), - value: 10, - optional: Some("A".to_string()), - }), - Arc::new(TestStruct { - name: "Item 2".to_string(), - value: 20, - optional: None, - }), - Arc::new(TestStruct { - name: "Item 3".to_string(), - value: 30, - optional: Some("B".to_string()), - }), - ]; - - let value_path_arc = value_path.clone().for_arc_root(); - - let sum: u32 = collection - .iter() - .filter_map(|item| value_path_arc.get(item).copied()) - .sum(); - - println!(" Sum of values: {}", sum); - assert_eq!(sum, 60, "Sum should be 60"); - println!("✓ Test 9 passed\n"); - - // ===== Test 10: Vec> with Mutation ===== - println!("--- Test 10: Vec> with Mutation ---"); - let mut box_collection: Vec> = vec![ - Box::new(TestStruct { - name: "Box 1".to_string(), - value: 100, - optional: None, - }), - Box::new(TestStruct { - name: "Box 2".to_string(), - value: 200, - optional: None, - }), - ]; - - let value_path_box_w = value_path_w.clone().for_box_root(); - - // Increment all values - for item in &mut box_collection { - let value = value_path_box_w.get_mut(item); - *value += 10; - } - - // Verify modifications - let new_sum: u32 = box_collection - .iter() - .filter_map(|item| value_path.clone().for_box_root().get(item).copied()) - .sum(); - - println!(" Sum after increment: {}", new_sum); - assert_eq!(new_sum, 320, "Sum should be 320 after increments"); - println!("✓ Test 10 passed\n"); - - // ===== Test 11: Vec> Filtering ===== - println!("--- Test 11: Vec> Filtering ---"); - let rc_collection: Vec> = vec![ - Rc::new(TestStruct { - name: "RC A".to_string(), - value: 5, - optional: Some("X".to_string()), - }), - Rc::new(TestStruct { - name: "RC B".to_string(), - value: 15, - optional: Some("Y".to_string()), - }), - Rc::new(TestStruct { - name: "RC C".to_string(), - value: 25, - optional: None, - }), - ]; - - let optional_path_rc = optional_path.clone().for_rc_root(); - - let with_optional: Vec<&Rc> = rc_collection - .iter() - .filter(|item| optional_path_rc.get(item).is_some()) - .collect(); - - println!(" Items with optional value: {}", with_optional.len()); - assert_eq!(with_optional.len(), 2, "Should find 2 items with optional"); - println!("✓ Test 11 passed\n"); - - // ===== Test 12: Multiple Container Types Together ===== - println!("--- Test 12: Mixed Container Types ---"); - - let arc_item = Arc::new(test_data.clone()); - let box_item = Box::new(test_data.clone()); - let rc_item = Rc::new(test_data.clone()); - - let name_path_arc_12 = name_path.clone().for_arc_root(); - let name_path_box_12 = name_path.clone().for_box_root(); - let name_path_rc_12 = name_path.clone().for_rc_root(); - - let arc_name = name_path_arc_12.get(&arc_item).unwrap(); - let box_name = name_path_box_12.get(&box_item).unwrap(); - let rc_name = name_path_rc_12.get(&rc_item).unwrap(); - - assert_eq!(arc_name, box_name, "Arc and Box should return same value"); - assert_eq!(box_name, rc_name, "Box and Rc should return same value"); - println!(" All containers return: '{}'", arc_name); - println!("✓ Test 12 passed\n"); - - // ===== Test 13: Result Readable ===== - println!("--- Test 13: Result with Readable KeyPath ---"); - let ok_data = Ok(test_data.clone()); - let err_data: Result = Err("Error occurred".to_string()); - - let name_path_result = name_path.clone().for_result::(); - - if let Some(name) = name_path_result.get(&ok_data) { - println!(" Result name (Ok): {}", name); - assert_eq!( - name, "Test", - "Result readable should return correct value for Ok" - ); - } - - if let Some(_) = name_path_result.get(&err_data) { - panic!("Result readable should return None for Err"); - } - println!("✓ Test 13 passed\n"); - - // ===== Test 14: Result Writable ===== - println!("--- Test 14: Result with Writable KeyPath ---"); - let mut ok_data_mut = Ok(test_data.clone()); - let mut err_data_mut: Result = Err("Error occurred".to_string()); - - let name_path_result_w = name_path_w.clone().for_result::(); - - if let Some(name) = name_path_result_w.get_mut(&mut ok_data_mut) { - println!(" Original Result name: {}", name); - *name = "Modified Result".to_string(); - println!(" Modified Result name: {}", name); - assert_eq!( - name, "Modified Result", - "Result writable should allow modification for Ok" - ); - } - - if name_path_result_w.get_mut(&mut err_data_mut).is_some() { - panic!("Result writable should return None for Err"); - } - println!("✓ Test 14 passed\n"); - - // ===== Test 15: Result Failable Readable ===== - println!("--- Test 15: Result with Failable Readable KeyPath ---"); - let ok_data_opt = Ok(TestStruct { - name: "Test".to_string(), - value: 42, - optional: Some("Optional Value".to_string()), - }); - let ok_data_none = Ok(TestStruct { - name: "Test".to_string(), - value: 42, - optional: None, - }); - let err_data_opt: Result = Err("Error occurred".to_string()); - - let optional_path_result = optional_path.clone().for_result::(); - - if let Some(opt_val) = optional_path_result.get(&ok_data_opt) { - println!(" Result optional (Some): {}", opt_val); - assert_eq!( - opt_val, "Optional Value", - "Result failable readable should return Some for Ok with Some" - ); - } - - if let Some(_) = optional_path_result.get(&ok_data_none) { - panic!("Result failable readable should return None for Ok with None"); - } - - if let Some(_) = optional_path_result.get(&err_data_opt) { - panic!("Result failable readable should return None for Err"); - } - println!("✓ Test 15 passed\n"); - - // ===== Test 16: Result Failable Writable ===== - println!("--- Test 16: Result with Failable Writable KeyPath ---"); - let mut ok_data_opt_mut = Ok(TestStruct { - name: "Test".to_string(), - value: 42, - optional: Some("Original".to_string()), - }); - let mut err_data_opt_mut: Result = Err("Error occurred".to_string()); - - let optional_path_result_w = optional_path_w.clone().for_result::(); - - if let Some(opt_val) = optional_path_result_w.get_mut(&mut ok_data_opt_mut) { - println!(" Original Result optional: {}", opt_val); - *opt_val = "Modified".to_string(); - println!(" Modified Result optional: {}", opt_val); - assert_eq!( - opt_val, "Modified", - "Result failable writable should allow modification for Ok with Some" - ); - } - - if optional_path_result_w - .get_mut(&mut err_data_opt_mut) - .is_some() - { - panic!("Result failable writable should return None for Err"); - } - println!("✓ Test 16 passed\n"); - - // ===== Test 17: Vec> Collection ===== - println!("--- Test 17: Vec> Collection ---"); - let result_collection: Vec> = vec![ - Ok(TestStruct { - name: "Success 1".to_string(), - value: 10, - optional: Some("A".to_string()), - }), - Err("Error 1".to_string()), - Ok(TestStruct { - name: "Success 2".to_string(), - value: 20, - optional: None, - }), - Err("Error 2".to_string()), - Ok(TestStruct { - name: "Success 3".to_string(), - value: 30, - optional: Some("B".to_string()), - }), - ]; - - let value_path_result = value_path.clone().for_result::(); - - let sum: u32 = result_collection - .iter() - .filter_map(|item| value_path_result.get(item).copied()) - .sum(); - - println!(" Sum of successful values: {}", sum); - assert_eq!(sum, 60, "Sum should be 60 (only successful results)"); - println!("✓ Test 17 passed\n"); - - println!("=== All 17 Tests Passed! ==="); -} diff --git a/examples/container_adapters.rs b/examples/container_adapters.rs deleted file mode 100644 index 3cd9a71..0000000 --- a/examples/container_adapters.rs +++ /dev/null @@ -1,339 +0,0 @@ -// Demonstrates using keypath adapters for smart pointers and containers -// This example shows how to: -// 1. Use for_arc() for Vec> collections -// 2. Use for_box() for Vec> collections -// 3. Use for_rc() for Vec> collections -// 4. Query and filter wrapped types -// 5. Compose keypaths with adapters -// cargo run --example container_adapters - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use std::rc::Rc; -use std::sync::Arc; - -#[derive(Debug, Clone, Kp)] -#[All] -struct Product { - id: u32, - name: String, - price: f64, - category: String, - in_stock: bool, -} - -#[derive(Debug, Clone, Kp)] -#[All] -struct User { - id: u32, - name: String, - email: String, - age: u32, -} - -fn main() { - println!("=== Container Adapter Demo ===\n"); - - // ===== Example 1: Vec> ===== - println!("--- Example 1: Vec> ---"); - - let products_arc: Vec> = vec![ - Arc::new(Product { - id: 1, - name: "Laptop".to_string(), - price: 999.99, - category: "Electronics".to_string(), - in_stock: true, - }), - Arc::new(Product { - id: 2, - name: "Mouse".to_string(), - price: 29.99, - category: "Electronics".to_string(), - in_stock: true, - }), - Arc::new(Product { - id: 3, - name: "Desk".to_string(), - price: 299.99, - category: "Furniture".to_string(), - in_stock: false, - }), - ]; - - // Create adapted keypaths for Arc - let name_path_arc = Product::name_r().for_arc_root(); - let price_path_arc = Product::price_r().for_arc_root(); - let category_path_arc = Product::category_r().for_arc_root(); - let in_stock_path_arc = Product::in_stock_r().for_arc_root(); - - println!("All products:"); - for product in &products_arc { - if let Some(name) = name_path_arc.get(product) { - if let Some(price) = price_path_arc.get(product) { - println!(" • {} - ${:.2}", name, price); - } - } - } - - // Filter Arc using adapted keypaths - let affordable_in_stock: Vec<&Arc> = products_arc - .iter() - .filter(|p| { - price_path_arc.get(p).map_or(false, |price| *price < 100.0) - && in_stock_path_arc.get(p).map_or(false, |stock| *stock) - }) - .collect(); - - println!("\nAffordable products in stock:"); - for product in affordable_in_stock { - if let Some(name) = name_path_arc.get(product) { - println!(" • {}", name); - } - } - - // ===== Example 2: Vec> ===== - println!("\n--- Example 2: Vec> ---"); - - let users_box: Vec> = vec![ - Box::new(User { - id: 1, - name: "Akash".to_string(), - email: "akash@example.com".to_string(), - age: 30, - }), - Box::new(User { - id: 2, - name: "Bob".to_string(), - email: "bob@example.com".to_string(), - age: 25, - }), - Box::new(User { - id: 3, - name: "Charlie".to_string(), - email: "charlie@example.com".to_string(), - age: 35, - }), - ]; - - // Create adapted keypaths for Box - let name_path_box = User::name_r().for_box_root(); - let age_path_box = User::age_r().for_box_root(); - let email_path_box = User::email_r().for_box_root(); - - println!("All users:"); - for user in &users_box { - if let Some(name) = name_path_box.get(user) { - if let Some(age) = age_path_box.get(user) { - println!(" • {} ({})", name, age); - } - } - } - - // Filter Box using adapted keypaths - let senior_users: Vec<&Box> = users_box - .iter() - .filter(|u| age_path_box.get(u).map_or(false, |age| *age >= 30)) - .collect(); - - println!("\nUsers 30+:"); - for user in senior_users { - if let Some(name) = name_path_box.get(user) { - if let Some(email) = email_path_box.get(user) { - println!(" • {} <{}>", name, email); - } - } - } - - // ===== Example 3: Vec> ===== - println!("\n--- Example 3: Vec> ---"); - - let products_rc: Vec> = vec![ - Rc::new(Product { - id: 4, - name: "Monitor".to_string(), - price: 349.99, - category: "Electronics".to_string(), - in_stock: true, - }), - Rc::new(Product { - id: 5, - name: "Chair".to_string(), - price: 199.99, - category: "Furniture".to_string(), - in_stock: true, - }), - ]; - - // Create adapted keypaths for Rc - let name_path_rc = Product::name_r().for_rc_root(); - let price_path_rc = Product::price_r().for_rc_root(); - let category_path_rc = Product::category_r().for_rc_root(); - - println!("Rc products:"); - for product in &products_rc { - if let Some(name) = name_path_rc.get(product) { - if let Some(category) = category_path_rc.get(product) { - println!(" • {} - {}", name, category); - } - } - } - - // ===== Example 4: Mutable Access with Box ===== - println!("\n--- Example 4: Mutable Access with Box ---"); - - let mut users_box_mut = users_box; - let name_path_box_w = User::name_w().for_box_root(); - let age_path_box_w = User::age_w().for_box_root(); - - // Modify through Box keypath - if let Some(user) = users_box_mut.get_mut(0) { - let name = name_path_box_w.get_mut(user); - println!(" Original name: {}", name); - *name = "Akash Smith".to_string(); - println!(" Modified name: {}", name); - - let age = age_path_box_w.get_mut(user); - *age += 1; - println!(" Incremented age to: {}", age); - } - - // ===== Example 5: Grouping by Category (Arc) ===== - println!("\n--- Example 5: Grouping Arc Products by Category ---"); - - use std::collections::HashMap; - let mut by_category: HashMap>> = HashMap::new(); - - for product in products_arc { - if let Some(category) = category_path_arc.get(&product) { - by_category - .entry(category.clone()) - .or_insert_with(Vec::new) - .push(product); - } - } - - for (category, products) in &by_category { - println!(" {}: {} products", category, products.len()); - for product in products { - if let Some(name) = name_path_arc.get(product) { - println!(" - {}", name); - } - } - } - - // ===== Example 6: Complex Filtering (Rc) ===== - println!("\n--- Example 6: Complex Filtering with Rc ---"); - - let expensive_electronics: Vec<&Rc> = products_rc - .iter() - .filter(|p| { - let is_electronics = category_path_rc - .get(p) - .map_or(false, |cat| cat == "Electronics"); - let is_expensive = price_path_rc.get(p).map_or(false, |price| *price > 200.0); - is_electronics && is_expensive - }) - .collect(); - - println!("Expensive electronics (Rc):"); - for product in expensive_electronics { - if let Some(name) = name_path_rc.get(product) { - if let Some(&price) = price_path_rc.get(product) { - println!(" • {} - ${:.2}", name, price); - } - } - } - - // ===== Example 7: Mixed Container Types ===== - println!("\n--- Example 7: Comparison Across Container Types ---"); - - let product_arc = Arc::new(Product { - id: 10, - name: "Keyboard".to_string(), - price: 79.99, - category: "Electronics".to_string(), - in_stock: true, - }); - - let product_box = Box::new(Product { - id: 11, - name: "Keyboard".to_string(), - price: 79.99, - category: "Electronics".to_string(), - in_stock: true, - }); - - let product_rc = Rc::new(Product { - id: 12, - name: "Keyboard".to_string(), - price: 79.99, - category: "Electronics".to_string(), - in_stock: true, - }); - - // All use the same underlying keypath, just adapted - println!("Arc: {}", name_path_arc.get(&product_arc).unwrap()); - println!( - "Box: {}", - Product::name_r().for_box_root().get(&product_box).unwrap() - ); - println!("Rc: {}", name_path_rc.get(&product_rc).unwrap()); - - // ===== Example 8: Practical Use Case - Shared State ===== - println!("\n--- Example 8: Simulating Shared State Pattern ---"); - - // Common pattern: Arc for shared ownership - let shared_products: Vec> = vec![ - Arc::new(Product { - id: 20, - name: "Headphones".to_string(), - price: 149.99, - category: "Electronics".to_string(), - in_stock: true, - }), - Arc::new(Product { - id: 21, - name: "Webcam".to_string(), - price: 89.99, - category: "Electronics".to_string(), - in_stock: false, - }), - ]; - - // Clone Arcs (cheap - just increments reference count) - let thread1_products = shared_products.clone(); - let thread2_products = shared_products.clone(); - - // Both threads can query using adapted keypaths - println!("Thread 1 view:"); - for product in &thread1_products { - if let Some(name) = name_path_arc.get(product) { - if let Some(in_stock) = Product::in_stock_r().for_arc_root().get(product) { - println!( - " • {} - {}", - name, - if *in_stock { - "Available" - } else { - "Out of stock" - } - ); - } - } - } - - println!("Thread 2 view (same data):"); - let available_count = thread2_products - .iter() - .filter(|p| { - Product::in_stock_r() - .for_arc_root() - .get(p) - .map_or(false, |s| *s) - }) - .count(); - println!(" Available products: {}", available_count); - - println!("\n✓ Container adapter demo complete!"); -} diff --git a/examples/deadlock_prevention_async.rs b/examples/deadlock_prevention_async.rs deleted file mode 100644 index b765aac..0000000 --- a/examples/deadlock_prevention_async.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Deadlock prevention with keypaths: async/parallel execution (Tokio). -//! -//! Demonstrates the same patterns as deadlock_prevention_sync, but with: -//! - Arc> instead of parking_lot::Mutex -//! - tokio::spawn for concurrent tasks -//! - .get().await, .set().await for async lock access -//! -//! Run: `cargo run --example deadlock_prevention_async --features tokio` - -#![cfg(feature = "tokio")] - -use key_paths_derive::Kp; -use std::sync::Arc; -use tokio::time::{Duration, sleep}; - -#[derive(Clone, Kp)] -struct Account { - balance: i32, - id: u64, -} - -#[derive(Clone, Kp)] -struct Bank { - account1: Arc>, - account2: Arc>, -} - -#[tokio::main(flavor = "multi_thread")] -async fn main() { - println!("=== Deadlock Prevention with Keypaths (Async, Parallel) ===\n"); - - let bank = Arc::new(Bank { - account1: Arc::new(tokio::sync::Mutex::new(Account { - balance: 1000, - id: 1, - })), - account2: Arc::new(tokio::sync::Mutex::new(Account { - balance: 2000, - id: 2, - })), - }); - - // --- 1. Eager Lock Release --- - println!("--- 1. Eager Lock Release (no deadlock) ---"); - let bank1 = bank.clone(); - let bank2 = bank.clone(); - - let handle1 = tokio::spawn(async move { - // Lock acc1, read balance, RELEASE (scope ends) - let balance1 = { - let acc1 = Bank::account1_async().get(&*bank1).await.unwrap(); - acc1.balance - }; - sleep(Duration::from_millis(5)).await; - // Now lock acc2 independently (use set — works with &Bank) - Bank::account2_async() - .set(&*bank1, |acc| acc.balance += balance1) - .await - .unwrap(); - let acc2_bal = Bank::account2_async().get(&*bank1).await.unwrap().balance; - println!(" Task 1: Transfer 1->2 done, acc2.balance = {}", acc2_bal); - }); - - let handle2 = tokio::spawn(async move { - let balance2 = { - let acc2 = Bank::account2_async().get(&*bank2).await.unwrap(); - acc2.balance - }; - sleep(Duration::from_millis(5)).await; - Bank::account1_async() - .set(&*bank2, |acc| acc.balance += balance2) - .await - .unwrap(); - let acc1_bal = Bank::account1_async().get(&*bank2).await.unwrap().balance; - println!(" Task 2: Transfer 2->1 done, acc1.balance = {}", acc1_bal); - }); - - handle1.await.unwrap(); - handle2.await.unwrap(); - println!(" ✓ No deadlock (eager release)\n"); - - // --- 2. Lock Ordering --- - println!("--- 2. Lock Ordering (consistent order by ID) ---"); - let bank3 = bank.clone(); - let bank4 = bank.clone(); - - let handle3 = tokio::spawn(async move { - let id1 = Bank::account1_async().get(&*bank3).await.unwrap().id; - let id2 = Bank::account2_async().get(&*bank3).await.unwrap().id; - if id1 < id2 { - Bank::account1_async() - .set(&*bank3, |acc| acc.balance += 10) - .await - .unwrap(); - Bank::account2_async() - .set(&*bank3, |acc| acc.balance -= 10) - .await - .unwrap(); - } else { - Bank::account2_async() - .set(&*bank3, |acc| acc.balance -= 10) - .await - .unwrap(); - Bank::account1_async() - .set(&*bank3, |acc| acc.balance += 10) - .await - .unwrap(); - } - println!(" Task 3: Ordered transfer done"); - }); - - let handle4 = tokio::spawn(async move { - let id1 = Bank::account1_async().get(&*bank4).await.unwrap().id; - let id2 = Bank::account2_async().get(&*bank4).await.unwrap().id; - if id1 < id2 { - Bank::account1_async() - .set(&*bank4, |acc| acc.balance -= 5) - .await - .unwrap(); - Bank::account2_async() - .set(&*bank4, |acc| acc.balance += 5) - .await - .unwrap(); - } else { - Bank::account2_async() - .set(&*bank4, |acc| acc.balance += 5) - .await - .unwrap(); - Bank::account1_async() - .set(&*bank4, |acc| acc.balance -= 5) - .await - .unwrap(); - } - println!(" Task 4: Ordered transfer done"); - }); - - handle3.await.unwrap(); - handle4.await.unwrap(); - println!(" ✓ No deadlock (lock ordering)\n"); - - // --- 3. Snapshot Pattern --- - println!("--- 3. Snapshot Pattern ---"); - 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); - if let (Some(b1), Some(b2)) = (b1, b2) { - println!(" Task 5: Snapshot acc1={}, acc2={}", b1, b2); - Bank::account2_async() - .set(&*bank5, |acc| acc.balance += 100) - .await - .unwrap(); - } - }); - - handle5.await.unwrap(); - println!(" ✓ Snapshot done\n"); - - println!("=== All async parallel patterns completed ==="); -} diff --git a/examples/deadlock_prevention_sync.rs b/examples/deadlock_prevention_sync.rs deleted file mode 100644 index f107b32..0000000 --- a/examples/deadlock_prevention_sync.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Deadlock prevention with keypaths: sync/parallel execution. -//! -//! Demonstrates: -//! 1. **Eager Lock Release** - Lock, read, release; then lock next. Prevents deadlock. -//! 2. **Lock Ordering** - Acquire locks in consistent order (by account ID). -//! 3. **Snapshot Pattern** - Copy data out, release lock, process without holding locks. -//! -//! Run: `cargo run --example deadlock_prevention_sync --features parking_lot` - -#![cfg(feature = "parking_lot")] - -use key_paths_derive::Kp; -use std::sync::Arc; -use std::thread; -use std::time::Duration; - -#[derive(Clone, Kp)] -struct Account { - balance: i32, - id: u64, -} - -#[derive(Clone, Kp)] -struct Bank { - account1: Arc>, - account2: Arc>, -} - -fn main() { - println!("=== Deadlock Prevention with Keypaths (Parallel) ===\n"); - - let bank = Arc::new(Bank { - account1: Arc::new(parking_lot::Mutex::new(Account { - balance: 1000, - id: 1, - })), - account2: Arc::new(parking_lot::Mutex::new(Account { - balance: 2000, - id: 2, - })), - }); - - // --- 1. Eager Lock Release --- - println!("--- 1. Eager Lock Release (no deadlock) ---"); - let bank1 = bank.clone(); - let bank2 = bank.clone(); - - let handle1 = thread::spawn(move || { - // Lock acc1, read balance, RELEASE (scope ends) - let balance1 = { - let acc1 = Bank::account1_lock().get(&*bank1).unwrap(); - acc1.balance - }; - thread::sleep(Duration::from_millis(5)); - // Now lock acc2 independently (use set() — works with &Bank, get_mut needs &mut Bank) - Bank::account2_lock() - .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 - ); - }); - - let handle2 = thread::spawn(move || { - let balance2 = { - let acc2 = Bank::account2_lock().get(&*bank2).unwrap(); - acc2.balance - }; - thread::sleep(Duration::from_millis(5)); - Bank::account1_lock() - .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 - ); - }); - - handle1.join().unwrap(); - handle2.join().unwrap(); - println!(" ✓ No deadlock (eager release)\n"); - - // --- 2. Lock Ordering --- - println!("--- 2. Lock Ordering (consistent order by ID) ---"); - let bank3 = bank.clone(); - let bank4 = bank.clone(); - - let handle3 = thread::spawn(move || { - // Always acquire lower ID first — update in consistent order - 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(); - } else { - 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"); - }); - - let handle4 = thread::spawn(move || { - 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(); - } else { - 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"); - }); - - handle3.join().unwrap(); - handle4.join().unwrap(); - println!(" ✓ No deadlock (lock ordering)\n"); - - // --- 3. Snapshot Pattern --- - println!("--- 3. Snapshot Pattern ---"); - let bank5 = bank.clone(); - - let handle5 = thread::spawn(move || { - let b1 = Bank::account1_lock().get(&*bank5).map(|a| a.balance); - let b2 = Bank::account2_lock().get(&*bank5).map(|a| a.balance); - if let (Some(b1), Some(b2)) = (b1, b2) { - println!(" Thread 5: Snapshot acc1={}, acc2={}", b1, b2); - Bank::account2_lock() - .set(&*bank5, |acc| acc.balance += 100) - .unwrap(); - } - }); - - handle5.join().unwrap(); - println!(" ✓ Snapshot done\n"); - - println!("=== All parallel patterns completed ==="); -} diff --git a/examples/deep_nesting_composition_example.rs b/examples/deep_nesting_composition_example.rs deleted file mode 100644 index 1d29c92..0000000 --- a/examples/deep_nesting_composition_example.rs +++ /dev/null @@ -1,402 +0,0 @@ -use keypaths_proc::Kp; -use parking_lot::RwLock; -use rust_keypaths::OptionalKeyPath; -use std::sync::Arc; - -// Deeply nested data structures for demonstration -#[derive(Kp, Clone, Debug)] -struct Address { - street: String, - city: String, - country: String, - coordinates: Option, -} - -#[derive(Kp, Clone, Debug)] -struct Coordinates { - latitude: f64, - longitude: f64, -} - -#[derive(Kp, Clone, Debug)] -struct Contact { - email: String, - phone: Option, - address: Address, -} - -#[derive(Kp, Clone, Debug)] -struct Department { - name: String, - budget: u64, - manager_id: Option, // Use ID instead of direct reference to avoid recursion -} - -#[derive(Kp, Clone, Debug)] -struct Employee { - id: u32, - name: String, - contact: Contact, - department_id: Option, // Use ID instead of direct reference to avoid recursion - salary: u64, -} - -#[derive(Kp, Clone, Debug)] -struct Company { - name: String, - headquarters: Address, - employees: Vec, - departments: Vec, -} - -#[derive(Kp, Clone, Debug)] -struct Organization { - company: Company, - subsidiaries: Vec, - global_contact: Contact, -} - -fn main() { - println!("🏗️ Deep Nesting KeyPath Composition Example"); - println!("============================================="); - - // Create a complex nested structure wrapped in RwLock - let organization = Arc::new(RwLock::new(Organization { - company: Company { - name: "TechCorp".to_string(), - headquarters: Address { - street: "123 Tech Street".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - coordinates: Some(Coordinates { - latitude: 37.7749, - longitude: -122.4194, - }), - }, - employees: vec![ - Employee { - id: 1, - name: "Akash Johnson".to_string(), - contact: Contact { - email: "akash@techcorp.com".to_string(), - phone: Some("+1-555-0101".to_string()), - address: Address { - street: "456 Employee Ave".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - coordinates: Some(Coordinates { - latitude: 37.7849, - longitude: -122.4094, - }), - }, - }, - department_id: Some(1), - salary: 120_000, - }, - Employee { - id: 2, - name: "Bob Smith".to_string(), - contact: Contact { - email: "bob@techcorp.com".to_string(), - phone: None, - address: Address { - street: "789 Developer Blvd".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - coordinates: None, - }, - }, - department_id: Some(1), - salary: 95_000, - }, - ], - departments: vec![ - Department { - name: "Engineering".to_string(), - budget: 1_000_000, - manager_id: None, - }, - Department { - name: "Marketing".to_string(), - budget: 500_000, - manager_id: None, - }, - ], - }, - subsidiaries: vec![Company { - name: "TechCorp Europe".to_string(), - headquarters: Address { - street: "456 European Ave".to_string(), - city: "London".to_string(), - country: "UK".to_string(), - coordinates: Some(Coordinates { - latitude: 51.5074, - longitude: -0.1278, - }), - }, - employees: vec![], - departments: vec![], - }], - global_contact: Contact { - email: "global@techcorp.com".to_string(), - phone: Some("+1-555-GLOBAL".to_string()), - address: Address { - street: "123 Tech Street".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - coordinates: Some(Coordinates { - latitude: 37.7749, - longitude: -122.4194, - }), - }, - }, - })); - - println!("\n🎯 Natural KeyPath Composition Examples"); - println!("======================================="); - - // Example 1: Simple composition - Company name - println!("\n1️⃣ Simple Composition - Company Name"); - println!("-------------------------------------"); - let company_name_path = Organization::company_fr().then(Company::name_fr()); - - { - let guard = organization.read(); - if let Some(name) = company_name_path.get(&*guard) { - println!("✅ Company name: {}", name); - } - } - - // Example 2: Two-level composition - Headquarters city - println!("\n2️⃣ Two-Level Composition - Headquarters City"); - println!("---------------------------------------------"); - let hq_city_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::city_r().to_optional()); - - { - let guard = organization.read(); - if let Some(city) = hq_city_path.get(&*guard) { - println!("✅ Headquarters city: {}", city); - } - } - - // Example 3: Three-level composition - Headquarters coordinates - println!("\n3️⃣ Three-Level Composition - Headquarters Coordinates"); - println!("----------------------------------------------------"); - let hq_lat_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Coordinates::latitude_r().to_optional()); - - { - let guard = organization.read(); - if let Some(latitude) = hq_lat_path.get(&*guard) { - println!("✅ Headquarters latitude: {}", latitude); - } - } - - // Example 4: Four-level composition - Global contact email - println!("\n4️⃣ Four-Level Composition - Global Contact Email"); - println!("------------------------------------------------"); - let global_email_path = Organization::global_contact_r() - .to_optional() - .then(OptionalKeyPath::new(|c: &Contact| Some(&c.email))); - - { - let guard = organization.read(); - if let Some(email) = global_email_path.get(&*guard) { - println!("✅ Global contact email: {}", email); - } - } - - // Example 5: Five-level composition - Global contact address coordinates - println!("\n5️⃣ Five-Level Composition - Global Contact Address Coordinates"); - println!("-------------------------------------------------------------"); - let global_coords_path = Organization::global_contact_r() - .to_optional() - .then(Contact::address_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Coordinates::latitude_r().to_optional()); - - { - let guard = organization.read(); - if let Some(latitude) = global_coords_path.get(&*guard) { - println!("✅ Global contact address latitude: {}", latitude); - } - } - - // Example 6: Working with collections - First department budget - println!("\n6️⃣ Working with Collections - First Department Budget"); - println!("---------------------------------------------------"); - - // Since Vec doesn't have get_r, we'll access the first department directly - // This demonstrates how to work with collections in a natural way - { - let guard = organization.read(); - let org = &*guard; - if let Some(first_dept) = org.company.departments.first() { - let dept_budget_path = Department::budget_r(); - let budget = dept_budget_path.get(&first_dept); - println!("✅ First department budget: ${}", budget); - } - } - - // Example 7: Working with employees - First employee contact - println!("\n7️⃣ Working with Employees - First Employee Contact"); - println!("-------------------------------------------------"); - - { - let guard = organization.read(); - let org = &*guard; - if let Some(first_employee) = org.company.employees.first() { - let employee_contact_path = Employee::contact_r() - .to_optional() - .then(OptionalKeyPath::new(|c: &Contact| Some(&c.email))); - if let Some(email) = employee_contact_path.get(&first_employee) { - println!("✅ First employee email: {}", email); - } - } - } - - // Example 8: Global contact with optional phone - println!("\n8️⃣ Global Contact with Optional Phone"); - println!("-------------------------------------"); - let global_phone_path = Organization::global_contact_r() - .to_optional() - .then(Contact::phone_fr()); - - { - let guard = organization.read(); - if let Some(phone) = global_phone_path.get(&*guard) { - println!("✅ Global contact phone: {}", phone); - } - } - - println!("\n🔄 Natural Composition Patterns"); - println!("==============================="); - - // Pattern 1: Building keypaths step by step (very natural) - println!("\n📝 Pattern 1: Step-by-Step Composition"); - println!("--------------------------------------"); - - // Start with organization - let org_path = Organization::company_r().to_optional(); - - // Add company level - let company_path = org_path.then(Company::headquarters_r().to_optional()); - - // Add headquarters level - let hq_path = company_path.then(Address::city_r().to_optional()); - - { - let guard = organization.read(); - if let Some(city) = hq_path.get(&*guard) { - println!("✅ Headquarters city (step-by-step): {}", city); - } - } - - // Pattern 2: Fluent composition (very readable) - println!("\n📝 Pattern 2: Fluent Composition"); - println!("-------------------------------"); - - let fluent_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::country_r().to_optional()); - - { - let guard = organization.read(); - if let Some(country) = fluent_path.get(&*guard) { - println!("✅ Headquarters country (fluent): {}", country); - } - } - - // Pattern 3: Reusable intermediate keypaths - println!("\n📝 Pattern 3: Reusable Intermediate KeyPaths"); - println!("-------------------------------------------"); - - // Create reusable base paths - let company_base = Organization::company_r().to_optional(); - let hq_base = company_base.then(Company::headquarters_r().to_optional()); - let address_base = hq_base.then(Address::coordinates_fr()); - - // Compose different paths using the same base - // Note: We recreate the base path since OptionalKeyPath doesn't implement Clone - let hq_lat_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Coordinates::latitude_r().to_optional()); - let hq_lng_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Coordinates::longitude_r().to_optional()); - - { - let guard = organization.read(); - if let Some(lat) = hq_lat_path.get(&*guard) { - println!("✅ HQ latitude (reusable): {}", lat); - } - if let Some(lng) = hq_lng_path.get(&*guard) { - println!("✅ HQ longitude (reusable): {}", lng); - } - } - - // Pattern 4: Working with multiple levels of Option - println!("\n📝 Pattern 4: Multiple Levels of Option"); - println!("-------------------------------------"); - - let optional_coords_path = Organization::company_r() - .to_optional() - .then(Company::headquarters_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Coordinates::latitude_r().to_optional()); - - { - let guard = organization.read(); - if let Some(latitude) = optional_coords_path.get(&*guard) { - println!("✅ HQ coordinates latitude: {}", latitude); - } else { - println!("✅ HQ has no coordinates"); - } - } - - // Pattern 5: Working with collections using iteration - println!("\n📝 Pattern 5: Working with Collections"); - println!("-------------------------------------"); - - { - let guard = organization.read(); - let org = &*guard; - - // Iterate through employees and use keypaths on each - for (i, employee) in org.company.employees.iter().enumerate() { - let employee_name_path = Employee::name_r(); - let employee_email_path = Employee::contact_r() - .to_optional() - .then(OptionalKeyPath::new(|c: &Contact| Some(&c.email))); - - let name = employee_name_path.get(&employee); - if let Some(email) = employee_email_path.get(&employee) { - println!("✅ Employee {}: {} ({})", i + 1, name, email); - } - } - } - - println!("\n💡 Key Takeaways for Natural Composition"); - println!("========================================"); - println!("1. Use .then() for natural chaining of keypaths"); - println!("2. Build complex paths step-by-step for clarity"); - println!("3. Create reusable intermediate keypaths"); - println!("4. Use failable keypaths (fr/fw) for Option types"); - println!("5. Fluent composition reads like natural language"); - println!("6. Deep nesting works seamlessly with RwLock guards"); - println!("7. Each .then() call adds one level of composition"); - println!("8. KeyPaths maintain type safety through all levels"); - println!("9. For collections, access elements first, then apply keypaths"); - println!("10. Multiple Option levels are handled naturally with failable keypaths"); -} diff --git a/examples/deep_readable_composition_example.rs b/examples/deep_readable_composition_example.rs deleted file mode 100644 index f1af450..0000000 --- a/examples/deep_readable_composition_example.rs +++ /dev/null @@ -1,496 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::OptionalKeyPath; -use std::sync::{Arc, RwLock}; - -#[derive(Kp, Clone, Debug)] -struct Location { - latitude: f64, - longitude: f64, - altitude: Option, -} - -#[derive(Kp, Clone, Debug)] -struct Address { - street: String, - city: String, - country: String, - postal_code: Option, - coordinates: Option, -} - -#[derive(Kp, Clone, Debug)] -struct Contact { - email: String, - phone: Option, - address: Address, - emergency_contact: Option>, -} - -#[derive(Kp, Clone, Debug)] -struct Department { - name: String, - budget: u64, - manager_id: Option, - location: Address, -} - -#[derive(Kp, Clone, Debug)] -struct Employee { - id: u32, - name: String, - position: String, - salary: u64, - contact: Contact, - department_id: Option, - supervisor_id: Option, -} - -#[derive(Kp, Clone, Debug)] -struct Company { - name: String, - founded_year: u32, - headquarters: Address, - employees: Vec, - departments: Vec, - global_contact: Contact, -} - -#[derive(Kp, Clone, Debug)] -struct Organization { - name: String, - company: Company, - subsidiaries: Vec, - main_contact: Contact, -} - -#[derive(Kp, Clone, Debug)] -struct BusinessGroup { - name: String, - organizations: Vec, - headquarters: Address, - ceo_contact: Contact, -} - -fn main() { - println!("🏗️ Deep Readable KeyPath Composition Example"); - println!("============================================="); - - // Create a deeply nested structure wrapped in RwLock - let business_group = Arc::new(RwLock::new(BusinessGroup { - name: "Global Tech Holdings".to_string(), - organizations: vec![Organization { - name: "TechCorp International".to_string(), - company: Company { - name: "TechCorp".to_string(), - founded_year: 2010, - headquarters: Address { - street: "123 Tech Street".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(52.0), - }), - }, - employees: vec![ - Employee { - id: 1, - name: "Akash Johnson".to_string(), - position: "Senior Engineer".to_string(), - salary: 120_000, - contact: Contact { - email: "akash@techcorp.com".to_string(), - phone: Some("+1-555-0101".to_string()), - address: Address { - street: "456 Employee Ave".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94110".to_string()), - coordinates: Some(Location { - latitude: 37.7849, - longitude: -122.4094, - altitude: Some(45.0), - }), - }, - emergency_contact: Some(Box::new(Contact { - email: "emergency@akash.com".to_string(), - phone: Some("+1-555-EMERGENCY".to_string()), - address: Address { - street: "789 Emergency St".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: None, - coordinates: None, - }, - emergency_contact: None, - })), - }, - department_id: Some(101), - supervisor_id: None, - }, - Employee { - id: 2, - name: "Bob Smith".to_string(), - position: "Marketing Manager".to_string(), - salary: 95_000, - contact: Contact { - email: "bob@techcorp.com".to_string(), - phone: None, - address: Address { - street: "321 Marketing Blvd".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94115".to_string()), - coordinates: Some(Location { - latitude: 37.7949, - longitude: -122.4294, - altitude: Some(38.0), - }), - }, - emergency_contact: None, - }, - department_id: Some(102), - supervisor_id: Some(1), - }, - ], - departments: vec![ - Department { - name: "Engineering".to_string(), - budget: 1_000_000, - manager_id: Some(1), - location: Address { - street: "100 Engineering Way".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(50.0), - }), - }, - }, - Department { - name: "Marketing".to_string(), - budget: 500_000, - manager_id: Some(2), - location: Address { - street: "200 Marketing Ave".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(48.0), - }), - }, - }, - ], - global_contact: Contact { - email: "global@techcorp.com".to_string(), - phone: Some("+1-555-GLOBAL".to_string()), - address: Address { - street: "1000 Corporate Plaza".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(55.0), - }), - }, - emergency_contact: None, - }, - }, - subsidiaries: vec![], - main_contact: Contact { - email: "main@techcorp-intl.com".to_string(), - phone: Some("+1-555-INTL".to_string()), - address: Address { - street: "500 International Blvd".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(60.0), - }), - }, - emergency_contact: None, - }, - }], - headquarters: Address { - street: "1000 Global Plaza".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(100.0), - }), - }, - ceo_contact: Contact { - email: "ceo@globaltech.com".to_string(), - phone: Some("+1-555-CEO".to_string()), - address: Address { - street: "2000 Executive Tower".to_string(), - city: "San Francisco".to_string(), - country: "USA".to_string(), - postal_code: Some("94105".to_string()), - coordinates: Some(Location { - latitude: 37.7749, - longitude: -122.4194, - altitude: Some(120.0), - }), - }, - emergency_contact: None, - }, - })); - - println!("\n🎯 Deep Readable KeyPath Composition Examples"); - println!("============================================="); - - // 1. Simple Composition - Business Group Name (1 level deep) - let group_name_path = BusinessGroup::name_r(); - group_name_path.with_arc_rwlock_direct(&business_group, |name| { - println!("1️⃣ Simple Composition - Business Group Name"); - println!("-------------------------------------------"); - println!("✅ Business group name: {}", name); - }); - - // 2. Two-Level Composition - First Organization Name (2 levels deep) - // We'll access the first organization directly since Vec doesn't have get_r - { - let guard = business_group.read().unwrap(); - let org = &*guard; - if let Some(first_org) = org.organizations.first() { - let org_name_path = Organization::name_r(); - let name = org_name_path.get(&first_org); - println!("\n2️⃣ Two-Level Composition - First Organization Name"); - println!("------------------------------------------------"); - println!("✅ First organization name: {}", name); - } - } - - // 3. Three-Level Composition - Company Name (3 levels deep) - let company_name_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::name_r().to_optional()); - company_name_path.with_arc_rwlock_direct(&business_group, |name| { - println!("\n3️⃣ Three-Level Composition - Company Name"); - println!("----------------------------------------"); - println!("✅ Company name: {}", name); - }); - - // 4. Four-Level Composition - Headquarters City (4 levels deep) - let hq_city_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::headquarters_r().to_optional()) - .then(Address::city_r().to_optional()); - hq_city_path.with_arc_rwlock_direct(&business_group, |city| { - println!("\n4️⃣ Four-Level Composition - Headquarters City"); - println!("---------------------------------------------"); - println!("✅ Headquarters city: {}", city); - }); - - // 5. Five-Level Composition - Headquarters Coordinates (5 levels deep, with Option) - let hq_lat_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::headquarters_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Location::latitude_r().to_optional()); - hq_lat_path.with_arc_rwlock_direct(&business_group, |latitude| { - println!("\n5️⃣ Five-Level Composition - Headquarters Coordinates"); - println!("--------------------------------------------------"); - println!("✅ Headquarters latitude: {}", latitude); - }); - - // 6. Six-Level Composition - First Employee Name (6 levels deep) - let first_employee_name_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::name_r().to_optional()); - first_employee_name_path.with_arc_rwlock_direct(&business_group, |name| { - println!("\n6️⃣ Six-Level Composition - First Employee Name"); - println!("---------------------------------------------"); - println!("✅ First employee name: {}", name); - }); - - // 7. Seven-Level Composition - First Employee Contact Email (7 levels deep) - let first_employee_email_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::contact_r().to_optional()) - .then(Contact::email_r().to_optional()); - first_employee_email_path.with_arc_rwlock_direct(&business_group, |email| { - println!("\n7️⃣ Seven-Level Composition - First Employee Contact Email"); - println!("-------------------------------------------------------"); - println!("✅ First employee email: {}", email); - }); - - // 8. Eight-Level Composition - First Employee Address City (8 levels deep) - let first_employee_city_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::contact_r().to_optional()) - .then(Contact::address_r().to_optional()) - .then(Address::city_r().to_optional()); - first_employee_city_path.with_arc_rwlock_direct(&business_group, |city| { - println!("\n8️⃣ Eight-Level Composition - First Employee Address City"); - println!("------------------------------------------------------"); - println!("✅ First employee city: {}", city); - }); - - // 9. Nine-Level Composition - First Employee Address Coordinates (9 levels deep, with Option) - let first_employee_lat_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::contact_r().to_optional()) - .then(Contact::address_r().to_optional()) - .then(Address::coordinates_fr()) - .then(Location::latitude_r().to_optional()); - first_employee_lat_path.with_arc_rwlock_direct(&business_group, |latitude| { - println!("\n9️⃣ Nine-Level Composition - First Employee Address Coordinates"); - println!("-------------------------------------------------------------"); - println!("✅ First employee address latitude: {}", latitude); - }); - - // 10. Ten-Level Composition - First Employee Emergency Contact Email (10 levels deep, with Option) - // Note: This example is simplified due to nested container limitations in the current implementation - let first_employee_emergency_email_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::contact_r().to_optional()) - .then(Contact::email_r().to_optional()); - first_employee_emergency_email_path.with_arc_rwlock_direct(&business_group, |email| { - println!("\n🔟 Ten-Level Composition - First Employee Contact Email (Simplified)"); - println!("-------------------------------------------------------------"); - println!("✅ First employee contact email: {}", email); - }); - - println!("\n🔄 Advanced Composition Patterns"); - println!("==============================="); - - // Pattern 1: Reusable Base Paths - println!("\n📝 Pattern 1: Reusable Base Paths"); - println!("--------------------------------"); - - let org_base = BusinessGroup::organizations_fr_at(0); - // Note: We recreate paths instead of cloning since OptionalKeyPath doesn't implement Clone - let company_base = - BusinessGroup::organizations_fr_at(0).then(Organization::company_r().to_optional()); - let employees_base = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_r().to_optional()); - let first_employee_base = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)); - - // Use the same base paths for different fields - let employee_name_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::name_r().to_optional()); - let employee_position_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::position_r().to_optional()); - let employee_salary_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::salary_r().to_optional()); - - employee_name_path.with_arc_rwlock_direct(&business_group, |name| { - println!("✅ Employee name (reusable base): {}", name); - }); - - employee_position_path.with_arc_rwlock_direct(&business_group, |position| { - println!("✅ Employee position (reusable base): {}", position); - }); - - employee_salary_path.with_arc_rwlock_direct(&business_group, |salary| { - println!("✅ Employee salary (reusable base): ${}", salary); - }); - - // Pattern 2: Multiple Option Levels - println!("\n📝 Pattern 2: Multiple Option Levels"); - println!("----------------------------------"); - - let emergency_phone_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::employees_fr_at(0)) - .then(Employee::contact_r().to_optional()) - .then(Contact::phone_fr()); - - emergency_phone_path.with_arc_rwlock_direct(&business_group, |phone| { - println!("✅ Emergency contact phone: {:?}", phone); - }); - - // Pattern 3: Department Information - println!("\n📝 Pattern 3: Department Information"); - println!("----------------------------------"); - - let first_dept_name_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::departments_fr_at(0)) - .then(Department::name_r().to_optional()); - - let first_dept_budget_path = BusinessGroup::organizations_fr_at(0) - .then(Organization::company_r().to_optional()) - .then(Company::departments_fr_at(0)) - .then(Department::budget_r().to_optional()); - - first_dept_name_path.with_arc_rwlock_direct(&business_group, |name| { - println!("✅ First department name: {}", name); - }); - - first_dept_budget_path.with_arc_rwlock_direct(&business_group, |budget| { - println!("✅ First department budget: ${}", budget); - }); - - // Pattern 4: CEO Contact Information - println!("\n📝 Pattern 4: CEO Contact Information"); - println!("-----------------------------------"); - - let ceo_email_path = BusinessGroup::ceo_contact_r() - .to_optional() - .then(Contact::email_r().to_optional()); - let ceo_phone_path = BusinessGroup::ceo_contact_r() - .to_optional() - .then(Contact::phone_fr()); - let ceo_address_city_path = BusinessGroup::ceo_contact_r() - .to_optional() - .then(Contact::address_r().to_optional()) - .then(Address::city_r().to_optional()); - - ceo_email_path.with_arc_rwlock_direct(&business_group, |email| { - println!("✅ CEO email: {}", email); - }); - - ceo_phone_path.with_arc_rwlock_direct(&business_group, |phone| { - println!("✅ CEO phone: {:?}", phone); - }); - - ceo_address_city_path.with_arc_rwlock_direct(&business_group, |city| { - println!("✅ CEO address city: {}", city); - }); - - println!("\n💡 Key Takeaways for Deep Readable Composition"); - println!("============================================="); - println!("1. KeyPaths can compose up to 10+ levels deep seamlessly"); - println!("2. Use .then() for natural chaining of keypaths"); - println!("3. Handle Option types with failable keypaths (fr/fw)"); - println!("4. Create reusable base paths for efficiency"); - println!("5. Deep nesting works perfectly with RwLock guards"); - println!("6. Each .then() call adds one level of composition"); - println!("7. KeyPaths maintain type safety through all levels"); - println!("8. Multiple Option levels are handled naturally"); - println!("9. Collections can be accessed with KeyPaths::get_r(index)"); - println!("10. Complex business hierarchies are easily navigable"); -} diff --git a/examples/deeply_nested_enum_arc_rwlock.rs b/examples/deeply_nested_enum_arc_rwlock.rs deleted file mode 100644 index ef8247c..0000000 --- a/examples/deeply_nested_enum_arc_rwlock.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! Example demonstrating deeply nested enums with Arc> case paths -//! -//! Run with: cargo run --example deeply_nested_enum_arc_rwlock - -use keypaths_proc::{Casepaths, Kp, WritableKeypaths}; -use std::sync::{Arc, RwLock}; - -// ========== DATA STRUCTURES ========== - -/// Top-level application state -#[derive(Debug, Kp, WritableKeypaths)] -struct AppState { - current_mode: AppMode, -} - -/// Application mode - can be in different states -#[derive(Debug, Casepaths)] -#[All] -enum AppMode { - /// Idle mode - no active session - Idle, - /// Loading mode - fetching data - Loading(String), - /// Active session with thread-safe state - Active(Arc>), -} - -/// A user session that can be accessed from multiple threads -#[derive(Debug, Kp, WritableKeypaths)] -struct Session { - user_name: String, - user_email: Option, - theme: Theme, - task_progress: Option, -} - -/// Theme selection - nested enum -#[derive(Debug, Clone, Casepaths)] -#[All] -enum Theme { - Light, - Dark, - Custom(CustomTheme), -} - -/// Custom theme configuration -#[derive(Debug, Clone, Kp, WritableKeypaths)] -struct CustomTheme { - primary_color: String, - font_size: u32, -} - -// ========== HELPER CONSTRUCTORS ========== - -impl AppState { - fn new_active() -> Self { - Self { - current_mode: AppMode::Active(Arc::new(RwLock::new(Session { - user_name: "Akash".to_string(), - user_email: Some("alice@example.com".to_string()), - theme: Theme::Custom(CustomTheme { - primary_color: "#3498db".to_string(), - font_size: 14, - }), - task_progress: Some(0.75), - }))), - } - } - - fn new_idle() -> Self { - Self { - current_mode: AppMode::Idle, - } - } -} - -fn main() { - println!("=== Deeply Nested Enum with Arc> Example ===\n"); - let app_state = AppState::new_active(); - // ========== READING ========== - // Use _fr() (failable readable) with chain_arc_rwlock_at_kp or chain_arc_rwlock_optional_at_kp - - // Read non-optional field through the chain - println!("Reading user_name (non-optional field):"); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_at_kp(Session::user_name_r()); // Use _r() for non-optional - kp.get(&app_state, |value| { - println!(" ✓ user_name = {:?}", value); - }); - - // Read optional field through the chain - println!("\nReading user_email (optional field):"); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_optional_at_kp(Session::user_email_fr()); // Use _fr() for optional - kp.get(&app_state, |value| { - println!(" ✓ user_email = {:?}", value); - }); - - // ========== WRITING (with full chain syntax!) ========== - // Use _w() or _fw() (writable) with chain_arc_rwlock_writable_at_kp or chain_arc_rwlock_writable_optional_at_kp - - // Write to non-optional field through the full chain - println!("\nWriting to user_name (using chain_arc_rwlock_writable_at_kp):"); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_writable_at_kp(Session::user_name_w()); // Use _w() for non-optional writable - kp.get_mut(&app_state, |value| { - *value = "Akash (Updated via chain!)".to_string(); - println!(" ✓ Updated user_name"); - }); - - // Write to optional field through the full chain - println!("\nWriting to user_email (using chain_arc_rwlock_writable_optional_at_kp):"); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_writable_optional_at_kp(Session::user_email_fw()); // Use _fw() for optional writable - kp.get_mut(&app_state, |value| { - *value = "updated@example.com".to_string(); - println!(" ✓ Updated user_email"); - }); - - // Verify the writes - println!("\nVerifying writes:"); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_at_kp(Session::user_name_r()); - kp.get(&app_state, |value| { - println!(" ✓ user_name = {:?}", value); - }); - - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_optional_at_kp(Session::user_email_fr()); - kp.get(&app_state, |value| { - println!(" ✓ user_email = {:?}", value); - }); - - // ========== IDLE STATE (Non-matching variant) ========== - println!("\nTesting with Idle state (non-matching variant):"); - let idle_state = AppState::new_idle(); - let kp = AppState::current_mode_fr() - .then(AppMode::active_r()) - .chain_arc_rwlock_at_kp(Session::user_name_r()); - let result = kp.get(&idle_state, |_| ()); - if result.is_none() { - println!(" ✓ Correctly returned None - enum is Idle, not Active"); - } - - println!("\n=== Example completed ==="); - println!("\nSyntax summary:"); - println!(" READING non-optional: .chain_arc_rwlock_at_kp(Session::field_r())"); - println!(" READING optional: .chain_arc_rwlock_optional_at_kp(Session::field_fr())"); - println!(" WRITING non-optional: .chain_arc_rwlock_writable_at_kp(Session::field_w())"); - println!( - " WRITING optional: .chain_arc_rwlock_writable_optional_at_kp(Session::field_fw())" - ); -} diff --git a/examples/derive_macros_new_features_example.rs b/examples/derive_macros_new_features_example.rs deleted file mode 100644 index 25c34cb..0000000 --- a/examples/derive_macros_new_features_example.rs +++ /dev/null @@ -1,298 +0,0 @@ -use keypaths_proc::{AnyKeypaths, Kp, PartialKp}; -use rust_keypaths::{ - AnyKeyPath, PartialKeyPath, PartialOptionalKeyPath, PartialWritableKeyPath, - PartialWritableOptionalKeyPath, -}; -use std::any::Any; -// cd /rust-key-paths && cargo run --example derive_macros_new_features_example 2>&1 | tail -20 -/// Example demonstrating the new derive macros for PartialKeyPath and AnyKeyPath -/// This example shows how to use the new #[derive(PartialKeypaths)] and #[derive(AnyKeypaths)] macros - -#[derive(Debug, Clone, Kp, PartialKp, AnyKeypaths)] -struct User { - id: u32, - name: String, - email: Option, - is_active: bool, - tags: Vec, - metadata: std::collections::HashMap, -} - -#[derive(Debug, Clone, Kp, PartialKp, AnyKeypaths)] -struct Product { - id: u64, - title: String, - price: f64, - in_stock: bool, - categories: Vec, -} - -fn main() { - println!("=== Derive Macros for New KeyPath Features ===\n"); - - let user = User { - id: 1, - name: "Akash".to_string(), - email: Some("akash@example.com".to_string()), - is_active: true, - tags: vec!["premium".to_string(), "verified".to_string()], - metadata: std::collections::HashMap::from([ - ("department".to_string(), "engineering".to_string()), - ("level".to_string(), "senior".to_string()), - ]), - }; - - let product = Product { - id: 101, - title: "Rust Programming Book".to_string(), - price: 49.99, - in_stock: true, - categories: vec!["programming".to_string(), "books".to_string()], - }; - - // Example 1: Using regular KeyPaths (existing functionality) - println!("--- 1. Regular KeyPaths (existing functionality) ---"); - - let name_path = User::name_r(); - let email_path = User::email_fr(); - let tags_path = User::tags_r(); - - let name = name_path.get(&user); - println!("User name: {}", name); - - if let Some(email) = email_path.get(&user) { - println!("User email: {:?}", email); - } - - let tags = tags_path.get(&user); - println!("User tags: {:?}", tags); - - // Example 2: Using PartialKeyPath derive macros - println!("\n--- 2. PartialKeyPath derive macros ---"); - - // Generated methods: field_partial_r(), field_partial_w(), field_partial_fr(), etc. - let name_partial = User::name_partial_r(); - let email_partial = User::email_partial_fr(); - let tags_partial = User::tags_partial_r(); - let metadata_partial = User::metadata_partial_r(); - - // Store different keypaths - note: email_partial is PartialOptionalKeyPath, others are PartialKeyPath - // For demonstration, we'll handle them separately - println!("Name (partial): {:?}", name_partial.get(&user).type_id()); - if let Some(email) = email_partial.get(&user) { - println!("Email (partial): {:?}", email.type_id()); - } - println!("Tags (partial): {:?}", tags_partial.get(&user).type_id()); - println!( - "Metadata (partial): {:?}", - metadata_partial.get(&user).type_id() - ); - - // Note: Different partial keypath types have different get() signatures - // PartialKeyPath::get() returns &dyn Any - // PartialOptionalKeyPath::get() returns Option<&dyn Any> - - // Example 3: Using AnyKeyPath derive macros - println!("\n--- 3. AnyKeyPath derive macros ---"); - - // Generated methods: field_any_r(), field_any_w(), field_any_fr(), etc. - let user_name_any = User::name_any_r(); - let user_email_any = User::email_any_fr(); - let product_title_any = Product::title_any_r(); - let product_price_any = Product::price_any_r(); - - // Store different keypaths from different types in the same collection (fully type-erased) - let any_keypaths: Vec = vec![ - user_name_any, - user_email_any, - product_title_any, - product_price_any, - ]; - - // Use any keypaths with full type erasure - for (i, keypath) in any_keypaths.iter().enumerate() { - // We need to box the data to use with AnyKeyPath - let user_boxed: Box = Box::new(user.clone()); - let product_boxed: Box = Box::new(product.clone()); - - // Try with user first (for user keypaths) - if i < 2 { - if let Some(value) = keypath.get(&*user_boxed) { - println!( - "Any keypath {} (user): {:?} (type: {})", - i, - value.type_id(), - keypath.kind_name() - ); - } - } else { - // Try with product (for product keypaths) - if let Some(value) = keypath.get(&*product_boxed) { - println!( - "Any keypath {} (product): {:?} (type: {})", - i, - value.type_id(), - keypath.kind_name() - ); - } - } - } - - // Example 4: Collection access with derive macros - println!("\n--- 4. Collection access with derive macros ---"); - - // Vec access with partial keypaths - let first_tag_partial = User::tags_partial_fr_at(0); - if let Some(tag) = first_tag_partial.get_as::(&user) { - println!("First tag (partial): {:?}", tag); - } - - // HashMap access with partial keypaths - let department_partial = User::metadata_partial_fr("department".to_string()); - if let Some(dept) = department_partial.get_as::(&user) { - println!("Department (partial): {:?}", dept); - } - - // Vec access with any keypaths - let first_tag_any = User::tags_any_fr_at(0); - let user_boxed: Box = Box::new(user.clone()); - if let Some(tag) = first_tag_any.get(&*user_boxed) { - println!("First tag (any): {:?}", tag); - } - - // Example 5: Writable keypaths with derive macros - println!("\n--- 5. Writable keypaths with derive macros ---"); - - let mut user_mut = user.clone(); - - // Using regular writable keypaths (not type-erased) - // Note: Writable keypaths are not generated by default - use regular KeyPath for reading - let name_r = User::name_r(); - let name_ref = name_r.get(&user_mut); - println!("Name (regular): {}", name_ref); - - // Note: Type-erased keypaths (PartialKeyPath, AnyKeyPath) return &dyn Any - // which cannot be directly assigned to. They are primarily for read-only access - // and dynamic keypath selection. For mutation, use regular KeyPaths. - - // Demonstrate that partial keypaths work for reading - let name_partial_r = User::name_partial_r(); - let name_ref = name_partial_r.get(&user_mut); - println!("Name via partial keypath: {:?}", name_ref.type_id()); - - // Example 6: Owned keypaths with derive macros - println!("\n--- 6. Owned keypaths with derive macros ---"); - - // Note: Owned keypath methods are not generated for PartialKeypaths - // as they require references, not owned values - let name_partial_r = User::name_partial_r(); - if let Some(name_ref) = name_partial_r.get_as::(&user) { - println!("Name (partial): {:?}", name_ref); - } - - // Note: Owned keypath methods are not generated for AnyKeypaths - // as they require references, not owned values - let name_any_r = User::name_any_r(); - let user_boxed: Box = Box::new(user.clone()); - if let Some(name_ref_any) = name_any_r.get(&*user_boxed) { - if let Some(name_str) = name_ref_any.downcast_ref::() { - println!("Name (any): {:?}", name_str); - } - } - - // Example 7: Mixed keypath types in collections - println!("\n--- 7. Mixed keypath types in collections ---"); - - // Create a collection of different keypath types - // Note: We can't easily mix PartialKeyPath and PartialOptionalKeyPath in the same collection - // So we'll demonstrate them separately - let name_partial = User::name_partial_r(); - let name_value = name_partial.get(&user); - println!( - "Mixed keypath 0 (partial, name): {:?}", - name_value.type_id() - ); - - let email_partial = User::email_partial_fr(); - if let Some(email_value) = email_partial.get(&user) { - println!( - "Mixed keypath 1 (partial, email): {:?}", - email_value.type_id() - ); - } - - let name_any = User::name_any_r(); - let user_boxed: Box = Box::new(user.clone()); - if let Some(name_value) = name_any.get(&*user_boxed) { - println!( - "Mixed keypath 2 (any, user name): {:?}", - name_value.type_id() - ); - } - - let title_any = Product::title_any_r(); - let product_boxed: Box = Box::new(product.clone()); - if let Some(title_value) = title_any.get(&*product_boxed) { - println!( - "Mixed keypath 3 (any, product title): {:?}", - title_value.type_id() - ); - } - - // Example 8: Dynamic keypath selection with derive macros - println!("\n--- 8. Dynamic keypath selection with derive macros ---"); - - // Note: We can't easily mix PartialKeyPath and PartialOptionalKeyPath in the same HashMap - // So we'll demonstrate dynamic access separately - for field_name in ["name", "tags", "metadata"] { - match field_name { - "name" => { - let keypath = User::name_partial_r(); - let value = keypath.get(&user); - println!( - "Dynamic access to {} (partial): {:?}", - field_name, - value.type_id() - ); - } - "tags" => { - let keypath = User::tags_partial_r(); - let value = keypath.get(&user); - println!( - "Dynamic access to {} (partial): {:?}", - field_name, - value.type_id() - ); - } - "metadata" => { - let keypath = User::metadata_partial_r(); - let value = keypath.get(&user); - println!( - "Dynamic access to {} (partial): {:?}", - field_name, - value.type_id() - ); - } - _ => {} - } - } - // Handle optional field separately - let email_keypath = User::email_partial_fr(); - if let Some(value) = email_keypath.get(&user) { - println!("Dynamic access to email (partial): {:?}", value.type_id()); - } - - println!("\n✅ Derive Macros for New KeyPath Features Example completed!"); - println!("📝 This example demonstrates:"); - println!(" • #[derive(PartialKeypaths)] - Generates field_partial_*() methods"); - println!(" • #[derive(AnyKeypaths)] - Generates field_any_*() methods"); - println!(" • Type-erased keypaths for collections of same Root type (PartialKeyPath)"); - println!( - " • Fully type-erased keypaths for collections of different Root types (AnyKeyPath)" - ); - println!(" • Collection access with indexed methods (field_partial_fr_at, field_any_fr_at)"); - println!(" • Writable and owned keypath variants"); - println!(" • Dynamic keypath selection and usage"); - println!(" • Mixed keypath types in collections"); - println!(" • Full integration with existing KeyPaths ecosystem!"); -} diff --git a/examples/display_demo.rs b/examples/display_demo.rs deleted file mode 100644 index 485817c..0000000 --- a/examples/display_demo.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Demonstrates the Display trait implementation for keypaths -// cargo run --example display_demo - -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -struct Person { - name: String, - age: Option, - address: Address, -} - -struct Address { - street: String, - city: Option, -} - -fn main() { - println!("=== KeyPath Display Examples ===\n"); - - // KeyPath (readable, non-optional) - let name_path: KeyPath = KeyPath::new(|p: &Person| &p.name); - println!("KeyPath: {}", name_path); - println!("Debug: {:?}\n", name_path); - - // OptionalKeyPath (readable, optional) - let age_path: OptionalKeyPath = - OptionalKeyPath::new(|p: &Person| p.age.as_ref()); - println!("OptionalKeyPath: {}", age_path); - println!("Debug: {:?}\n", age_path); - - // WritableKeyPath (mutable, non-optional) - let name_writable: WritableKeyPath = - WritableKeyPath::new(|p: &mut Person| &mut p.name); - println!("WritableKeyPath: {}", name_writable); - println!("Debug: {:?}\n", name_writable); - - // WritableOptionalKeyPath (mutable, optional) - let age_writable: WritableOptionalKeyPath = - WritableOptionalKeyPath::new(|p: &mut Person| p.age.as_mut()); - println!("WritableOptionalKeyPath: {}", age_writable); - println!("Debug: {:?}\n", age_writable); - - // Chained keypaths - let street_path: KeyPath = KeyPath::new(|p: &Person| &p.address.street); - println!("Chained KeyPath: {}", street_path); - - let city_path: OptionalKeyPath = - OptionalKeyPath::new(|p: &Person| p.address.city.as_ref()); - println!("Chained OptionalKeyPath: {}", city_path); - - println!("\n=== Display Format Benefits ==="); - println!("- Shows keypath type (KeyPath, OptionalKeyPath, etc.)"); - println!("- Shows source and target types"); - println!("- Simplified type names (without full module paths)"); - println!("- Useful for debugging and logging"); -} diff --git a/examples/enum_casepaths_macros.rs b/examples/enum_casepaths_macros.rs deleted file mode 100644 index 9970540..0000000 --- a/examples/enum_casepaths_macros.rs +++ /dev/null @@ -1,44 +0,0 @@ -use keypaths_proc::{Casepaths, Kp}; - -#[derive(Debug, Clone, Kp)] -struct User { - id: u32, - name: String, -} - -#[derive(Debug, Casepaths)] -#[All] -enum Status { - Active(User), - Inactive, -} - -fn main() { - let status = Status::Active(User { - id: 1, - name: "Ada".into(), - }); - - let kp_active = Status::active_r(); - let active_name = Status::active_r().then(User::name_r().to_optional()); - if let Some(name) = active_name.get(&status) { - println!("Active name = {:?}", name); - } - - let mut status2 = Status::Active(User { - id: 2, - name: "Bob".into(), - }); - let kp_active_w = Status::active_w(); - if let Some(user) = kp_active_w.get_mut(&mut status2) { - user.name.push_str("_edited"); - } - println!("Status2 = {:?}", status2); - - // Embedding via readable enum - use the generated embed function - let embedded = Status::active_embed(User { - id: 3, - name: "Cleo".into(), - }); - println!("Embedded = {:?}", embedded); -} diff --git a/examples/enum_keypath_example.rs b/examples/enum_keypath_example.rs deleted file mode 100644 index 67b4250..0000000 --- a/examples/enum_keypath_example.rs +++ /dev/null @@ -1,71 +0,0 @@ -use rust_keypaths::EnumKeyPath; - -#[derive(Debug, Clone)] -struct User { - id: u32, - name: String, -} - -#[derive(Debug)] -enum Status { - Active(User), - Inactive(()), -} - -#[derive(Debug)] -enum SomeOtherStatus { - Active(String), - Inactive, -} - -fn main() { - // ---------- EnumPath ---------- - let cp = EnumKeyPath::readable_enum( - |user: User| Status::Active(user), - |u: &Status| match u { - Status::Active(e) => Some(e), - _ => None, - }, - ); - // let cp2 = enum_keypath!(Status::Inactive(())); - let cp2 = EnumKeyPath::readable_enum( - |_unit: ()| Status::Inactive(()), - |u| match u { - Status::Inactive(_) => Some(&()), - _ => None, - }, - ); - - // let cp3 = enum_keypath!(SomeOtherStatus::Active(String)); - let cp3 = EnumKeyPath::readable_enum( - |s: String| SomeOtherStatus::Active(s), - |u| match u { - SomeOtherStatus::Active(e) => Some(e), - _ => None, - }, - ); - if let Some(x) = cp3.get(&SomeOtherStatus::Active("Hello".to_string())) { - println!("Active: {:?}", x); - } - - // let cp4 = enum_keypath!(SomeOtherStatus::Inactive); - let cp4 = EnumKeyPath::readable_enum(|_unit: ()| SomeOtherStatus::Inactive, |_u| None::<&()>); - if let Some(_x) = cp4.get(&SomeOtherStatus::Inactive) { - println!("Inactive: {:?}", _x); - } - - let status = Status::Active(User { - id: 42, - name: "Charlie".to_string(), - }); - - if let Some(u) = cp.get(&status) { - println!("Extracted user: {:?}", u); - } - - let new_status = cp.embed(User { - id: 99, - name: "Diana".to_string(), - }); - println!("Embedded back: {:?}", new_status); -} diff --git a/examples/enum_keypath_macros.rs b/examples/enum_keypath_macros.rs deleted file mode 100644 index 7ce1bc6..0000000 --- a/examples/enum_keypath_macros.rs +++ /dev/null @@ -1,95 +0,0 @@ -use key_paths_derive::Kp; - -#[derive(Debug, Clone, Kp)] -struct User { - id: u32, - name: String, -} - -#[derive(Debug)] -enum Status { - Active(User), - Inactive(()), -} - -#[derive(Debug)] -enum SomeOtherStatus { - Active(String), - Inactive, -} - -fn main() { - // Derive-generated keypaths for struct fields - let user_name_kp = User::name(); - let user_id_kp = User::id(); - - let user = User { - id: 7, - name: "Ada".into(), - }; - println!("user.name via kp = {:?}", user_name_kp.get(&user)); - println!("user.id via kp = {:?}", user_id_kp.get(&user)); - - // Enum keypaths using core enum helpers - let status_active_user = EnumKeyPath::readable_enum( - |u: User| Status::Active(u), - |s: &Status| match s { - Status::Active(u) => Some(u), - _ => None, - }, - ); - - let status_inactive_unit = EnumKeyPath::readable_enum( - |u: ()| Status::Inactive(u), - |s: &Status| match s { - Status::Inactive(u) => Some(u), - _ => None, - }, - ); - - let some_other_active = EnumKeyPath::readable_enum( - |v: String| SomeOtherStatus::Active(v), - |s: &SomeOtherStatus| match s { - SomeOtherStatus::Active(v) => Some(v), - _ => None, - }, - ); - - let status = Status::Active(User { - id: 42, - name: "Grace".into(), - }); - - if let Some(u) = status_active_user.get(&status) { - println!("Extracted user: {:?}", u); - } - - // Compose enum kp with derived struct field kp (consumes the keypath) - let active_user_name = EnumKeyPath::readable_enum( - |u: User| Status::Active(u), - |s: &Status| match s { - Status::Active(u) => Some(u), - _ => None, - }, - ) - .to_optional() - .then(User::name().to_optional()); - - println!("Active user name = {:?}", active_user_name.get(&status)); - - let embedded = status_active_user.embed(User { - id: 99, - name: "Lin".into(), - }); - println!("Embedded back: {:?}", embedded); - - let greeting = SomeOtherStatus::Active("Hello".to_string()); - if let Some(x) = some_other_active.get(&greeting) { - println!("SomeOtherStatus::Active: {:?}", x); - } - - let inactive = Status::Inactive(()); - if let Some(x) = status_inactive_unit.get(&inactive) { - println!("Status::Inactive: {:?}", x); - } -} diff --git a/examples/failable.rs b/examples/failable.rs deleted file mode 100644 index 2790e09..0000000 --- a/examples/failable.rs +++ /dev/null @@ -1,46 +0,0 @@ -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug)] -struct Engine { - horsepower: u32, -} -#[derive(Debug)] -struct Car { - engine: Option, -} -#[derive(Debug)] -struct Garage { - car: Option, -} - -fn main() { - let garage = Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 120 }), - }), - }; - - let kp_car = OptionalKeyPath::new(|g: &Garage| g.car.as_ref()); - let kp_engine = OptionalKeyPath::new(|c: &Car| c.engine.as_ref()); - let kp_hp = OptionalKeyPath::new(|e: &Engine| Some(&e.horsepower)); - - // Compose: Garage -> Car -> Engine -> horsepower - let kp = kp_car.then(kp_engine).then(kp_hp); - - let kp2 = OptionalKeyPath::new(|g: &Garage| { - g.car - .as_ref() - .and_then(|c| c.engine.as_ref()) - .and_then(|e| Some(&e.horsepower)) - }); - - if let Some(hp) = kp.get(&garage) { - println!("{hp:?}"); - } - - if let Some(hp) = kp2.get(&garage) { - println!("{hp:?}"); - } - - println!("{garage:?}"); -} diff --git a/examples/failable_combined_example.rs b/examples/failable_combined_example.rs deleted file mode 100644 index 744d20f..0000000 --- a/examples/failable_combined_example.rs +++ /dev/null @@ -1,90 +0,0 @@ -use rust_keypaths::{ - FailableCombinedKeyPath, KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath, -}; - -// Example struct to demonstrate FailableCombined keypath -#[derive(Debug, Clone)] -struct Person { - name: String, - age: u32, - address: Option, -} - -impl Person { - // Constructor - fn new(name: String, age: u32, address: Option) -> Self { - Self { name, age, address } - } -} - -fn main() { - println!("🔗 FailableCombined KeyPath Example"); - println!("====================================="); - - // Create a person with an address - let mut person = Person::new("Akash".to_string(), 30, Some("123 Main St".to_string())); - - // Create a FailableCombined keypath for the address field - // This keypath can handle all three access patterns: readable, writable, and owned - let address_keypath = FailableCombinedKeyPath::failable_combined( - // Readable closure - returns Option<&String> - |person: &Person| person.address.as_ref(), - // Writable closure - returns Option<&mut String> - |person: &mut Person| person.address.as_mut(), - // Owned closure - returns Option (takes ownership of Person, moves only the address) - |person: Person| person.address, - ); - - println!("\n📖 Testing Readable Access:"); - // Test readable access - if let Some(address) = address_keypath.get(&person) { - println!("✅ Address (readable): {}", address); - } else { - println!("❌ No address found"); - } - - println!("\n✏️ Testing Writable Access:"); - // Test writable access - if let Some(address) = address_keypath.get_mut(&mut person) { - *address = "456 Oak Ave".to_string(); - println!("✅ Address updated to: {}", address); - } else { - println!("❌ Could not get mutable reference to address"); - } - - println!("\n📦 Testing Owned Access:"); - // Test owned access - this moves both the keypath and the root - // We need to clone the keypath since get_failable_owned consumes it - if let Some(owned_address) = address_keypath.clone().get_failable_owned(person.clone()) { - println!("✅ Got owned address: {}", owned_address); - // The person is still available since we cloned it - println!("✅ Person is still available: {:?}", person); - } else { - println!("❌ Could not get owned address"); - } - - println!("\n🧪 Testing with Person without Address:"); - // Test with a person without an address - let person_no_address = Person::new("Bob".to_string(), 25, None); - - println!("📖 Readable access (no address):"); - if let Some(_address) = address_keypath.get(&person_no_address) { - println!("✅ Address found: {}", _address); - } else { - println!("❌ No address found (expected)"); - } - - println!("\n📦 Owned access (no address):"); - if let Some(_owned_address) = address_keypath.get_failable_owned(person_no_address) { - println!("✅ Got owned address: {}", _owned_address); - } else { - println!("❌ No address found (expected)"); - } - - println!("\n✨ Key Benefits of FailableCombined:"); - println!("1. 🔍 Readable: Get immutable references when available"); - println!("2. ✏️ Writable: Get mutable references when available"); - println!("3. 📦 Owned: Get owned values without moving the root"); - println!("4. 🛡️ Failable: All operations return Option for safe handling"); - println!("5. 🎯 Combined: One keypath handles all three access patterns"); -} diff --git a/examples/failable_macros.rs b/examples/failable_macros.rs deleted file mode 100644 index 27ec3c1..0000000 --- a/examples/failable_macros.rs +++ /dev/null @@ -1,71 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::{OptionalKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Kp)] -#[All] -struct Engine { - horsepower: u32, -} - -#[derive(Debug, Kp)] -#[All] -struct Car { - engine: Option, -} - -#[derive(Debug, Kp)] -#[All] -struct Garage { - car: Option, -} - -#[derive(Debug, Kp)] -#[All] -struct City { - garage: Option, -} - -fn main() { - let mut city = City { - garage: Some(Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 120 }), - }), - }), - }; - - // Failable readable chain via derive-generated methods on Option fields - let city_hp = City::garage_fr() - .then(Garage::car_fr()) - .then(Car::engine_fr()) - .then(Engine::horsepower_r().to_optional()); - - println!("Horsepower (read) = {:?}", city_hp.get(&city)); - - // Failable writable chain via derive-generated methods - let garage_fw = City::garage_fw(); - let car_fw = Garage::car_fw(); - let engine_fw = Car::engine_fw(); - let hp_w = Engine::horsepower_w(); - - if let Some(garage) = garage_fw.get_mut(&mut city) { - if let Some(car) = car_fw.get_mut(garage) { - if let Some(engine) = engine_fw.get_mut(car) { - let hp = hp_w.get_mut(engine); - *hp += 30; - } - } - } - - println!("City after hp increment = {:?}", city); - - // Demonstrate short-circuiting when any Option is None - let mut city2 = City { garage: None }; - println!("Missing chain get = {:?}", city_hp.get(&city2)); - if let Some(garage) = garage_fw.get_mut(&mut city2) { - // won't run - let _ = garage; - } else { - println!("No garage to mutate"); - } -} diff --git a/examples/failable_writable.rs b/examples/failable_writable.rs deleted file mode 100644 index ac8e9fa..0000000 --- a/examples/failable_writable.rs +++ /dev/null @@ -1,36 +0,0 @@ -use rust_keypaths::WritableOptionalKeyPath; - -#[derive(Debug)] -struct Engine { - horsepower: u32, -} -#[derive(Debug)] -struct Car { - engine: Option, -} -#[derive(Debug)] -struct Garage { - car: Option, -} - -fn main() { - let mut garage = Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 120 }), - }), - }; - - let kp_car = WritableOptionalKeyPath::new(|g: &mut Garage| g.car.as_mut()); - let kp_engine = WritableOptionalKeyPath::new(|c: &mut Car| c.engine.as_mut()); - let kp_hp = WritableOptionalKeyPath::new(|e: &mut Engine| Some(&mut e.horsepower)); - - // Compose: Garage -> Car -> Engine -> horsepower - let kp = kp_car.then(kp_engine).then(kp_hp); - - println!("{garage:?}"); - if let Some(hp) = kp.get_mut(&mut garage) { - *hp = 200; - } - - println!("{garage:?}"); -} diff --git a/examples/failable_writable_macros.rs b/examples/failable_writable_macros.rs deleted file mode 100644 index 286a9ec..0000000 --- a/examples/failable_writable_macros.rs +++ /dev/null @@ -1,68 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -#[Writable] -struct Engine { - horsepower: u32, -} - -#[derive(Debug, Kp)] -#[Writable] -struct Car { - engine: Option, -} - -#[derive(Debug, Kp)] -#[Writable] -struct Garage { - car: Option, -} - -#[derive(Debug, Kp)] -#[Writable] -struct City { - garage: Option, -} - -fn main() { - // Start with everything present - let mut city = City { - garage: Some(Garage { - car: Some(Car { - engine: Some(Engine { horsepower: 180 }), - }), - }), - }; - - // Build a failable-writable chain using derive-generated methods - let garage_fw = City::garage_fw(); - let car_fw = Garage::car_fw(); - let engine_fw = Car::engine_fw(); - let hp_fw = Engine::horsepower_fw(); - - // Mutate through the entire chain (only if each Option is Some) - if let Some(garage) = garage_fw.get_mut(&mut city) { - if let Some(car) = car_fw.get_mut(garage) { - if let Some(engine) = engine_fw.get_mut(car) { - if let Some(hp) = hp_fw.get_mut(engine) { - *hp += 20; - } - } - } - } - - println!("City after failable_writable chain mutation = {:?}", city); - - // Show short-circuit: with a missing link, nothing happens - let mut city_missing = City { garage: None }; - if let Some(garage) = garage_fw.get_mut(&mut city_missing) { - if let Some(car) = car_fw.get_mut(garage) { - if let Some(engine) = engine_fw.get_mut(car) { - if let Some(hp) = hp_fw.get_mut(engine) { - *hp += 1000; // won't be reached - } - } - } - } - println!("City with missing path unchanged = {:?}", city_missing); -} diff --git a/examples/failiblity.rs b/examples/failiblity.rs deleted file mode 100644 index 913d450..0000000 --- a/examples/failiblity.rs +++ /dev/null @@ -1,21 +0,0 @@ -use keypaths_proc::Kp; - -// #[derive(Kp)] -struct SomeStruct { - f1: String, -} - -#[derive(Kp)] -enum SomeEnum { - active, - passive(String), -} - -impl SomeEnum { - fn active() {} -} - -fn main() { - let x = SomeEnum::active; - let y = SomeEnum::active(); -} diff --git a/examples/for_option_example.rs b/examples/for_option_example.rs deleted file mode 100644 index dcc64ba..0000000 --- a/examples/for_option_example.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Example demonstrating the for_option adapter method -// Run with: cargo run --example for_option_example - -use rust_keypaths::{ - KeyPath, OptionalKeyPath, WithContainer, WritableKeyPath, WritableOptionalKeyPath, -}; - -#[derive(Debug, Clone)] -struct User { - name: String, - age: u32, - email: Option, -} - -#[derive(Debug, Clone)] -struct Profile { - user: Option, - settings: Option, -} - -fn main() { - println!("=== For Option Adapter Example ===\n"); - - // Create test data - let user = User { - name: "Akash".to_string(), - age: 30, - email: Some("akash@example.com".to_string()), - }; - - let profile = Profile { - user: Some(user.clone()), - settings: Some("dark_mode".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: Basic Option Usage ===== - println!("--- Example 1: Basic Option Usage ---"); - - let mut option_user: Option = Some(user.clone()); - - // Use for_option to create a keypath that works with Option - let name_option_path = name_path.clone().for_option(); - - // Access name from Option using get_ref - if let Some(name) = name_option_path.get(&option_user) { - println!(" Name from Option: {}", name); - } - - // ===== Example 2: Writable Option Usage ===== - println!("--- Example 2: Writable Option Usage ---"); - - let mut option_user_mut: Option = Some(user.clone()); - - // Use for_option with writable keypath - let name_option_path_w = name_path_w.clone().for_option(); - - // Modify name in Option using get_mut - if let Some(name) = name_option_path_w.get_mut(&mut option_user_mut) { - *name = "Akash Updated".to_string(); - println!(" Updated name in Option: {}", name); - } - - // ===== Example 3: Failable KeyPath with Option ===== - println!("--- Example 3: Failable KeyPath with Option ---"); - - let option_user_with_email: Option = Some(User { - name: "Bob".to_string(), - age: 25, - email: Some("bob@example.com".to_string()), - }); - - // Use failable keypath with for_option - let email_option_path = email_path.clone().for_option(); - - // Access email from Option using get_ref - if let Some(email) = email_option_path.get(&option_user_with_email) { - println!(" Email from Option: {}", email); - } else { - println!(" No email in user"); - } - - // ===== Example 4: None Option Handling ===== - println!("--- Example 4: None Option Handling ---"); - - let none_user: Option = None; - - // Try to access name from None Option using get_ref - if name_option_path.get(&none_user).is_some() { - println!(" Name from None Option"); - } else { - println!(" Correctly handled None Option"); - } - - // ===== Example 5: Collection of Options ===== - println!("--- Example 5: Collection of Options ---"); - - let option_users: Vec> = vec![ - Some(User { - name: "Charlie".to_string(), - age: 35, - email: Some("charlie@example.com".to_string()), - }), - None, - Some(User { - name: "Diana".to_string(), - age: 28, - email: None, - }), - ]; - - // Process names from collection of Options using get_ref - let mut names = Vec::new(); - for option_user in &option_users { - if let Some(name) = name_option_path.get(&option_user) { - names.push(name.clone()); - } - } - println!(" User names from Option collection: {:?}", names); - - // ===== Example 6: Nested Option Structure ===== - println!("--- Example 6: Nested Option Structure ---"); - - let mut option_profile: Option = Some(profile.clone()); - - // Create a keypath that goes through Option -> Option -> String - let profile_user_name_path = - OptionalKeyPath::new(|p: &Profile| p.user.as_ref()).then(name_path.clone().to_optional()); - - // Use for_option to work with Option - let profile_name_option_path = profile_user_name_path.for_option(); - - // Access nested name through Option using get_ref - if let Some(name) = profile_name_option_path.get(&option_profile) { - println!(" Nested name from Option: {}", name); - } - - // ===== Example 7: Mutable Nested Option ===== - println!("--- Example 7: Mutable Nested Option ---"); - - let mut option_profile_mut: Option = Some(profile.clone()); - - // Create a writable keypath for nested Option -> Option -> String - let profile_user_name_path_w = WritableOptionalKeyPath::new(|p: &mut Profile| p.user.as_mut()) - .then(name_path_w.clone().to_optional()); - - // Use for_option to work with Option - let profile_name_option_path_w = profile_user_name_path_w.for_option(); - - // Modify nested name through Option using get_mut - if let Some(name) = profile_name_option_path_w.get_mut(&mut option_profile_mut) { - *name = "Akash Profile".to_string(); - println!(" Updated nested name in Option: {}", name); - } - - // ===== Example 8: Composition with for_option ===== - println!("--- Example 8: Composition with for_option ---"); - - let option_user_comp: Option = Some(user.clone()); - - // Compose keypaths: Option -> User -> Option -> String - let composed_path = name_path - .clone() - .for_option() // KeyPaths, &String> - .then(OptionalKeyPath::new(|s: &String| Some(s))); // KeyPaths, &String> - - // This creates a complex nested Option structure - println!(" Composed keypath created successfully"); - - // ===== Example 9: Error Handling ===== - println!("--- Example 9: Error Handling ---"); - - // Test with None at different levels - let none_profile: Option = None; - if profile_name_option_path.get(&none_profile).is_some() { - println!(" Name from None Profile"); - } else { - println!(" Correctly handled None Profile"); - } - - // Test with Profile containing None user - let profile_with_none_user = Profile { - user: None, - settings: Some("light_mode".to_string()), - }; - let option_profile_none_user: Option = Some(profile_with_none_user); - - if profile_name_option_path - .get(&option_profile_none_user) - .is_some() - { - println!(" Name from Profile with None user"); - } else { - println!(" Correctly handled Profile with None user"); - } - - println!("=== All Examples Completed Successfully! ==="); -} diff --git a/examples/form_binding.rs b/examples/form_binding.rs deleted file mode 100644 index 7571f0c..0000000 --- a/examples/form_binding.rs +++ /dev/null @@ -1,448 +0,0 @@ -// Demonstrates binding UI fields to data model properties using keypaths -// This example shows how to: -// 1. Create a generic form binding system without hardcoded access patterns -// 2. Support multiple field types (String, bool, numbers) -// 3. Implement two-way data binding (read and write) -// 4. Build reusable form validators -// 5. Track field-level changes -// cargo run --example form_binding - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Clone, Kp)] -#[All] -struct UserProfile { - name: String, - email: String, - age: u32, - settings: UserSettings, -} - -#[derive(Debug, Clone, Kp)] -#[All] -struct UserSettings { - notifications_enabled: bool, - theme: String, - font_size: u32, -} - -// Generic form field that binds to any field type -// Uses type erasure to store keypaths with different closure types -struct FormField -where - F: Clone + std::fmt::Display + 'static, -{ - read_path: Box Option>, - write_path: Box Result<(), String>>, - label: &'static str, - field_name: &'static str, - validator: fn(&F) -> Result<(), String>, -} - -impl FormField -where - F: Clone + std::fmt::Display + 'static, -{ - fn new( - read_path: OptionalKeyPath, - write_path: WritableOptionalKeyPath, - label: &'static str, - field_name: &'static str, - validator: fn(&F) -> Result<(), String>, - ) -> Self - where - FR: for<'r> Fn(&'r T) -> Option<&'r F> + 'static, - FW: for<'r> Fn(&'r mut T) -> Option<&'r mut F> + 'static, - { - Self { - read_path: Box::new(move |t: &T| read_path.get(t).cloned()), - write_path: Box::new(move |t: &mut T, value: F| { - // Validate first - (validator)(&value)?; - // Then write - if let Some(target) = write_path.get_mut(t) { - *target = value; - Ok(()) - } else { - Err(format!("Failed to write to field '{}'", field_name)) - } - }), - label, - field_name, - validator, - } - } - - // Read current value from the model - fn read(&self, model: &T) -> Option { - (self.read_path)(model) - } - - // Write new value to the model - fn write(&self, model: &mut T, value: F) -> Result<(), String> { - (self.write_path)(model, value) - } - - // Validate without writing - fn validate(&self, value: &F) -> Result<(), String> { - (self.validator)(value) - } -} - -// Form binding system that manages multiple fields -struct FormBinding { - string_fields: Vec>, - bool_fields: Vec>, - u32_fields: Vec>, -} - -impl FormBinding { - fn new() -> Self { - Self { - string_fields: Vec::new(), - bool_fields: Vec::new(), - u32_fields: Vec::new(), - } - } - - fn add_string_field(&mut self, field: FormField) { - self.string_fields.push(field); - } - - fn add_bool_field(&mut self, field: FormField) { - self.bool_fields.push(field); - } - - fn add_u32_field(&mut self, field: FormField) { - self.u32_fields.push(field); - } - - // Display current form state - fn display(&self, model: &T) - where - T: std::fmt::Debug, - { - println!("Current Form State:"); - println!("─────────────────────────────────────"); - - for field in &self.string_fields { - if let Some(value) = field.read(model) { - println!(" {}: '{}'", field.label, value); - } - } - - for field in &self.u32_fields { - if let Some(value) = field.read(model) { - println!(" {}: {}", field.label, value); - } - } - - for field in &self.bool_fields { - if let Some(value) = field.read(model) { - println!(" {}: {}", field.label, if value { "Yes" } else { "No" }); - } - } - - println!("─────────────────────────────────────"); - } - - // Update a string field by name - fn update_string(&self, model: &mut T, field_name: &str, value: String) -> Result<(), String> { - for field in &self.string_fields { - if field.field_name == field_name { - return field.write(model, value); - } - } - Err(format!("Field '{}' not found", field_name)) - } - - // Update a bool field by name - fn update_bool(&self, model: &mut T, field_name: &str, value: bool) -> Result<(), String> { - for field in &self.bool_fields { - if field.field_name == field_name { - return field.write(model, value); - } - } - Err(format!("Field '{}' not found", field_name)) - } - - // Update a u32 field by name - fn update_u32(&self, model: &mut T, field_name: &str, value: u32) -> Result<(), String> { - for field in &self.u32_fields { - if field.field_name == field_name { - return field.write(model, value); - } - } - Err(format!("Field '{}' not found", field_name)) - } - - // Validate all fields - fn validate_all(&self, model: &T) -> Result<(), Vec> { - let mut errors = Vec::new(); - - for field in &self.string_fields { - if let Some(value) = field.read(model) { - if let Err(e) = field.validate(&value) { - errors.push(format!("{}: {}", field.label, e)); - } - } - } - - for field in &self.u32_fields { - if let Some(value) = field.read(model) { - if let Err(e) = field.validate(&value) { - errors.push(format!("{}: {}", field.label, e)); - } - } - } - - for field in &self.bool_fields { - if let Some(value) = field.read(model) { - if let Err(e) = field.validate(&value) { - errors.push(format!("{}: {}", field.label, e)); - } - } - } - - if errors.is_empty() { - Ok(()) - } else { - Err(errors) - } - } -} - -// Create the form binding for UserProfile -fn create_user_profile_form() -> FormBinding { - let mut form = FormBinding::new(); - - // String field: name - form.add_string_field(FormField::new( - UserProfile::name_fr(), - UserProfile::name_fw(), - "Full Name", - "name", - |s| { - if s.len() >= 2 { - Ok(()) - } else { - Err("Name must be at least 2 characters".into()) - } - }, - )); - - // String field: email - form.add_string_field(FormField::new( - UserProfile::email_fr(), - UserProfile::email_fw(), - "Email Address", - "email", - |s| { - if s.contains('@') && s.contains('.') { - Ok(()) - } else { - Err("Invalid email format".into()) - } - }, - )); - - // Number field: age - form.add_u32_field(FormField::new( - UserProfile::age_fr(), - UserProfile::age_fw(), - "Age", - "age", - |&age| { - if age >= 13 && age <= 120 { - Ok(()) - } else { - Err("Age must be between 13 and 120".into()) - } - }, - )); - - // String field: theme (nested) - form.add_string_field(FormField::new( - UserProfile::settings_fr().then(UserSettings::theme_fr()), - UserProfile::settings_fw().then(UserSettings::theme_fw()), - "Theme", - "theme", - |s| { - if ["light", "dark", "auto"].contains(&s.as_str()) { - Ok(()) - } else { - Err("Theme must be 'light', 'dark', or 'auto'".into()) - } - }, - )); - - // Number field: font_size (nested) - form.add_u32_field(FormField::new( - UserProfile::settings_fr().then(UserSettings::font_size_fr()), - UserProfile::settings_fw().then(UserSettings::font_size_fw()), - "Font Size", - "font_size", - |&size| { - if (10..=24).contains(&size) { - Ok(()) - } else { - Err("Font size must be between 10 and 24".into()) - } - }, - )); - - // Bool field: notifications (nested) - form.add_bool_field(FormField::new( - UserProfile::settings_fr().then(UserSettings::notifications_enabled_fr()), - UserProfile::settings_fw().then(UserSettings::notifications_enabled_fw()), - "Notifications", - "notifications", - |_| Ok(()), // No validation needed for bool - )); - - form -} - -fn main() { - println!("=== Form Binding Demo ===\n"); - - // Create initial user profile - let mut profile = UserProfile { - name: "Akash".to_string(), - email: "akash@example.com".to_string(), - age: 28, - settings: UserSettings { - notifications_enabled: true, - theme: "dark".to_string(), - font_size: 14, - }, - }; - - // Create form binding - let form = create_user_profile_form(); - - // Display initial state - println!("=== Initial State ==="); - form.display(&profile); - - // Validate initial state - println!("\n=== Validating Initial State ==="); - match form.validate_all(&profile) { - Ok(_) => println!("✓ All fields valid"), - Err(errors) => { - println!("✗ Validation errors:"); - for error in errors { - println!(" - {}", error); - } - } - } - - // Update various fields through the binding system - println!("\n=== Updating Fields ==="); - - // Update name - match form.update_string(&mut profile, "name", "Akash Johnson".to_string()) { - Ok(_) => println!("✓ Updated name successfully"), - Err(e) => println!("✗ Failed to update name: {}", e), - } - - // Update email - match form.update_string( - &mut profile, - "email", - "akash.johnson@example.com".to_string(), - ) { - Ok(_) => println!("✓ Updated email successfully"), - Err(e) => println!("✗ Failed to update email: {}", e), - } - - // Update age - match form.update_u32(&mut profile, "age", 29) { - Ok(_) => println!("✓ Updated age successfully"), - Err(e) => println!("✗ Failed to update age: {}", e), - } - - // Update theme (nested field) - match form.update_string(&mut profile, "theme", "light".to_string()) { - Ok(_) => println!("✓ Updated theme successfully"), - Err(e) => println!("✗ Failed to update theme: {}", e), - } - - // Update font size (nested field) - match form.update_u32(&mut profile, "font_size", 16) { - Ok(_) => println!("✓ Updated font size successfully"), - Err(e) => println!("✗ Failed to update font size: {}", e), - } - - // Update notifications (nested field) - match form.update_bool(&mut profile, "notifications", false) { - Ok(_) => println!("✓ Updated notifications successfully"), - Err(e) => println!("✗ Failed to update notifications: {}", e), - } - - // Display updated state - println!("\n=== Updated State ==="); - form.display(&profile); - - // Try invalid updates - println!("\n=== Testing Validation ==="); - - // Try to set invalid name - match form.update_string(&mut profile, "name", "A".to_string()) { - Ok(_) => println!("✓ Updated name successfully"), - Err(e) => println!("✗ Failed to update name: {}", e), - } - - // Try to set invalid email - match form.update_string(&mut profile, "email", "not-an-email".to_string()) { - Ok(_) => println!("✓ Updated email successfully"), - Err(e) => println!("✗ Failed to update email: {}", e), - } - - // Try to set invalid age - match form.update_u32(&mut profile, "age", 5) { - Ok(_) => println!("✓ Updated age successfully"), - Err(e) => println!("✗ Failed to update age: {}", e), - } - - // Try to set invalid theme - match form.update_string(&mut profile, "theme", "rainbow".to_string()) { - Ok(_) => println!("✓ Updated theme successfully"), - Err(e) => println!("✗ Failed to update theme: {}", e), - } - - // Try to set invalid font size - match form.update_u32(&mut profile, "font_size", 50) { - Ok(_) => println!("✓ Updated font size successfully"), - Err(e) => println!("✗ Failed to update font size: {}", e), - } - - // Final state (should be unchanged due to validation errors) - println!("\n=== Final State (After Invalid Updates) ==="); - form.display(&profile); - - // Demonstrate two-way binding by reading values - println!("\n=== Two-Way Binding Demo ==="); - println!("Reading values through form binding:"); - - for field in &form.string_fields { - if let Some(value) = field.read(&profile) { - println!(" {}: '{}'", field.label, value); - } - } - - for field in &form.u32_fields { - if let Some(value) = field.read(&profile) { - println!(" {}: {}", field.label, value); - } - } - - for field in &form.bool_fields { - if let Some(value) = field.read(&profile) { - println!(" {}: {}", field.label, value); - } - } - - println!("\n✓ Form binding demo complete!"); -} diff --git a/examples/hashmap_keypath.rs b/examples/hashmap_keypath.rs deleted file mode 100644 index 4d9b25f..0000000 --- a/examples/hashmap_keypath.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::collections::HashMap; - -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -// use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use keypaths_proc::{Casepaths, Kp}; - -#[derive(Debug, Kp)] -#[All] -struct SomeComplexStruct { - scsf: HashMap, -} - -impl SomeComplexStruct { - // fn scsf_fr() -> KeyPaths { - // OptionalKeyPath::new( - // |root: & SomeComplexStruct| - // { - // root.scsf.first() - // } - // ) - // } - - // fn scsf_fr_at(index: String) -> KeyPaths { - // OptionalKeyPath::new( - // move |root: & SomeComplexStruct| - // { - // root.scsf.get(&index) - // } - // ) - // } - - // fn scsf_fw() -> KeyPaths { - // WritableOptionalKeyPath::new( - // |root: &mut SomeComplexStruct| - // { - // root.scsf.first_mut() - // } - // ) - // } - - // fn scsf_fw_at(index: String) -> KeyPaths - // { - // WritableOptionalKeyPath::new( - // move |root: &mut SomeComplexStruct| - // { - // root.scsf.get_mut(&index) - // } - // ) - // } -} -impl SomeComplexStruct { - fn new() -> Self { - let mut x = HashMap::new(); - x.insert( - "0".to_string(), - SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }, - ); - - x.insert( - "1".to_string(), - SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }, - ); - - Self { scsf: x } - } -} - -#[derive(Debug, Kp)] -#[All] -struct SomeOtherStruct { - sosf: OneMoreStruct, -} - -#[derive(Debug, Casepaths)] -#[All] -enum SomeEnum { - A(Vec), - B(DarkStruct), -} - -#[derive(Debug, Kp)] -#[All] -struct OneMoreStruct { - omsf: String, - omse: SomeEnum, -} - -#[derive(Debug, Kp)] -#[All] -struct DarkStruct { - dsf: String, -} - -fn main() { - let op = SomeComplexStruct::scsf_fw_at("1".to_string()) - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_w()) - .then(DarkStruct::dsf_fw()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get_mut(&mut instance) { - *omsf = String::from("we can change the field with the other way unlocked by keypaths"); - } - println!("instance = {:?}", instance); - - let op = SomeComplexStruct::scsf_fw_at("0".to_string()) - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_w()) - .then(DarkStruct::dsf_fw()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get_mut(&mut instance) { - *omsf = String::from("we can change the field with the other way unlocked by keypaths"); - } - println!("instance = {:?}", instance); -} diff --git a/examples/iters.rs b/examples/iters.rs deleted file mode 100644 index dcb1525..0000000 --- a/examples/iters.rs +++ /dev/null @@ -1,30 +0,0 @@ -use rust_keypaths::{KeyPath, WritableKeyPath}; - -struct Garage { - cars: Vec, -} - -fn main() { - let kp = KeyPath::new(|g: &Garage| &g.cars); - let mut g = Garage { - cars: vec!["BMW".into(), "Tesla".into(), "Audi".into()], - }; - - // Immutable iteration - // if let Some(iter) = kp.iter::(&g) { - if let Some(iter) = kp.iter(&g) { - for c in iter { - println!("car: {}", c); - } - } - - // Mutable iteration - let kp_mut = WritableKeyPath::new(|g: &mut Garage| &mut g.cars); - if let Some(iter) = kp_mut.iter_mut(&mut g) { - for c in iter { - c.push_str(" 🚗"); - } - } - - println!("{:?}", g.cars); -} diff --git a/examples/join_query_builder.rs b/examples/join_query_builder.rs deleted file mode 100644 index 872c8fb..0000000 --- a/examples/join_query_builder.rs +++ /dev/null @@ -1,588 +0,0 @@ -// Demonstrates JOIN operations between collections using keypaths -// This example shows how to: -// 1. Perform inner joins between collections -// 2. Perform left joins with optional results -// 3. Join on matching field values -// 4. Create multi-table queries -// 5. Use keypaths for type-safe join conditions -// cargo run --example join_query_builder - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use std::collections::HashMap; - -// Database schema: Users, Orders, Products -#[derive(Debug, Clone, Kp)] -struct User { - id: u32, - name: String, - email: String, - city: String, -} - -#[derive(Debug, Clone, Kp)] -struct Order { - id: u32, - user_id: u32, - product_id: u32, - quantity: u32, - total: f64, -} - -#[derive(Debug, Clone, Kp)] -struct Product { - id: u32, - name: String, - price: f64, - category: String, -} - -// Join result types -#[derive(Debug, Clone)] -struct UserOrder { - user_name: String, - user_email: String, - order_id: u32, - quantity: u32, - total: f64, -} - -#[derive(Debug, Clone)] -struct OrderDetail { - order_id: u32, - user_name: String, - product_name: String, - quantity: u32, - price: f64, - total: f64, -} - -#[derive(Debug, Clone)] -struct UserOrderCount { - user_name: String, - user_city: String, - order_count: usize, - total_spent: f64, -} - -#[derive(Debug, Clone)] -struct CategorySales { - category: String, - total_orders: usize, - total_revenue: f64, - unique_customers: usize, -} - -// Generic join builder -struct JoinQuery<'a, L: 'static, R: 'static> { - left: &'a [L], - right: &'a [R], -} - -impl<'a, L: Clone, R: Clone> JoinQuery<'a, L, R> { - fn new(left: &'a [L], right: &'a [R]) -> Self { - Self { left, right } - } - - // Inner join: returns only matching pairs - fn inner_join( - &self, - left_key: KeyPath, - right_key: KeyPath, - mapper: F, - ) -> Vec - where - K: Eq + std::hash::Hash + Clone + 'static, - F: Fn(&L, &R) -> O, - PL: for<'r> Fn(&'r L) -> &'r K + 'static, - PR: for<'r> Fn(&'r R) -> &'r K + 'static, - { - // Build index for right side for O(n) lookup - let mut right_index: HashMap> = HashMap::new(); - for item in self.right.iter() { - let key = right_key.get(item).clone(); - right_index.entry(key).or_insert_with(Vec::new).push(item); - } - - // Join left with indexed right - let mut results = Vec::new(); - for left_item in self.left.iter() { - let key = left_key.get(left_item).clone(); - if let Some(right_items) = right_index.get(&key) { - for right_item in right_items { - results.push(mapper(left_item, right_item)); - } - } - } - - results - } - - // Left join: returns all left items, with optional right matches - fn left_join( - &self, - left_key: KeyPath, - right_key: KeyPath, - mapper: F, - ) -> Vec - where - K: Eq + std::hash::Hash + Clone + 'static, - F: Fn(&L, Option<&R>) -> O, - PL: for<'r> Fn(&'r L) -> &'r K + 'static, - PR: for<'r> Fn(&'r R) -> &'r K + 'static, - { - // Build index for right side - let mut right_index: HashMap> = HashMap::new(); - for item in self.right.iter() { - let key = right_key.get(item).clone(); - right_index.entry(key).or_insert_with(Vec::new).push(item); - } - - // Join left with indexed right - let mut results = Vec::new(); - for left_item in self.left.iter() { - let key = left_key.get(left_item).clone(); - if let Some(right_items) = right_index.get(&key) { - for right_item in right_items { - results.push(mapper(left_item, Some(right_item))); - } - } else { - results.push(mapper(left_item, None)); - } - } - - results - } - - // Filter join: only matching pairs that satisfy a predicate - fn inner_join_where( - &self, - left_key: KeyPath, - right_key: KeyPath, - predicate: P, - mapper: F, - ) -> Vec - where - K: Eq + std::hash::Hash + Clone + 'static, - F: Fn(&L, &R) -> O, - P: Fn(&L, &R) -> bool, - PL: for<'r> Fn(&'r L) -> &'r K + 'static, - PR: for<'r> Fn(&'r R) -> &'r K + 'static, - { - // Build index for right side - let mut right_index: HashMap> = HashMap::new(); - for item in self.right.iter() { - let key = right_key.get(item).clone(); - right_index.entry(key).or_insert_with(Vec::new).push(item); - } - - // Join left with indexed right, applying predicate - let mut results = Vec::new(); - for left_item in self.left.iter() { - let key = left_key.get(left_item).clone(); - if let Some(right_items) = right_index.get(&key) { - for right_item in right_items { - if predicate(left_item, right_item) { - results.push(mapper(left_item, right_item)); - } - } - } - } - - results - } -} - -// Helper function for creating sample data -fn create_sample_data() -> (Vec, Vec, Vec) { - let users = vec![ - User { - id: 1, - name: "Akash".to_string(), - email: "akash@example.com".to_string(), - city: "New York".to_string(), - }, - User { - id: 2, - name: "Bob".to_string(), - email: "bob@example.com".to_string(), - city: "San Francisco".to_string(), - }, - User { - id: 3, - name: "Charlie".to_string(), - email: "charlie@example.com".to_string(), - city: "New York".to_string(), - }, - User { - id: 4, - name: "Diana".to_string(), - email: "diana@example.com".to_string(), - city: "Boston".to_string(), - }, - ]; - - let products = vec![ - Product { - id: 101, - name: "Laptop".to_string(), - price: 999.99, - category: "Electronics".to_string(), - }, - Product { - id: 102, - name: "Mouse".to_string(), - price: 29.99, - category: "Electronics".to_string(), - }, - Product { - id: 103, - name: "Desk Chair".to_string(), - price: 199.99, - category: "Furniture".to_string(), - }, - Product { - id: 104, - name: "Monitor".to_string(), - price: 299.99, - category: "Electronics".to_string(), - }, - Product { - id: 105, - name: "Keyboard".to_string(), - price: 79.99, - category: "Electronics".to_string(), - }, - ]; - - let orders = vec![ - Order { - id: 1001, - user_id: 1, - product_id: 101, - quantity: 1, - total: 999.99, - }, - Order { - id: 1002, - user_id: 1, - product_id: 102, - quantity: 2, - total: 59.98, - }, - Order { - id: 1003, - user_id: 2, - product_id: 103, - quantity: 1, - total: 199.99, - }, - Order { - id: 1004, - user_id: 2, - product_id: 104, - quantity: 1, - total: 299.99, - }, - Order { - id: 1005, - user_id: 3, - product_id: 102, - quantity: 3, - total: 89.97, - }, - Order { - id: 1006, - user_id: 1, - product_id: 105, - quantity: 1, - total: 79.99, - }, - Order { - id: 1007, - user_id: 3, - product_id: 101, - quantity: 1, - total: 999.99, - }, - ]; - - (users, orders, products) -} - -fn main() { - println!("=== Join Query Builder Demo ===\n"); - - let (users, orders, products) = create_sample_data(); - - println!("Database:"); - println!(" Users: {}", users.len()); - println!(" Orders: {}", orders.len()); - println!(" Products: {}\n", products.len()); - - // Join 1: Inner join Users and Orders - println!("--- Join 1: Users with Their Orders ---"); - let user_orders = JoinQuery::new(&users, &orders).inner_join( - User::id_r(), - Order::user_id_r(), - |user, order| UserOrder { - user_name: user.name.clone(), - user_email: user.email.clone(), - order_id: order.id, - quantity: order.quantity, - total: order.total, - }, - ); - - for uo in &user_orders { - println!( - " • {} - Order #{} - {} items - ${:.2}", - uo.user_name, uo.order_id, uo.quantity, uo.total - ); - } - println!("Total: {} user-order pairs", user_orders.len()); - - // Join 2: Three-way join (Orders -> Users, Orders -> Products) - println!("\n--- Join 2: Complete Order Details (3-Way Join) ---"); - - // First join: Orders with Users - let orders_with_users = JoinQuery::new(&orders, &users).inner_join( - Order::user_id_r(), - User::id_r(), - |order, user| (order.clone(), user.clone()), - ); - - // Second join: (Orders+Users) with Products - let mut order_details = Vec::new(); - for (order, user) in &orders_with_users { - for product in &products { - if order.product_id == product.id { - order_details.push(OrderDetail { - order_id: order.id, - user_name: user.name.clone(), - product_name: product.name.clone(), - quantity: order.quantity, - price: product.price, - total: order.total, - }); - } - } - } - - for od in &order_details { - println!( - " • Order #{}: {} bought {} x {} @ ${:.2} = ${:.2}", - od.order_id, od.user_name, od.quantity, od.product_name, od.price, od.total - ); - } - - // Join 3: Left join to show all users (including those without orders) - println!("\n--- Join 3: All Users with Order Count (Left Join) ---"); - - // Use left_join to get all users with their orders (or None) - let user_order_pairs = JoinQuery::new(&users, &orders).left_join( - User::id_r(), - Order::user_id_r(), - |user, order| (user.clone(), order.map(|o| o.clone())), - ); - - // Group by user to count orders - let mut user_stats: HashMap = HashMap::new(); - for (user, order) in &user_order_pairs { - let entry = user_stats - .entry(user.id) - .or_insert_with(|| (user.name.clone(), user.city.clone(), 0, 0.0)); - if let Some(order) = order { - entry.2 += 1; // order count - entry.3 += order.total; // total spent - } - } - - let mut user_order_stats: Vec<_> = user_stats - .into_iter() - .map(|(_, (name, city, count, total))| UserOrderCount { - user_name: name, - user_city: city, - order_count: count, - total_spent: total, - }) - .collect(); - - user_order_stats.sort_by(|a, b| a.user_name.cmp(&b.user_name)); - - for stat in &user_order_stats { - if stat.order_count > 0 { - println!( - " • {} ({}) - {} orders - ${:.2} total", - stat.user_name, stat.user_city, stat.order_count, stat.total_spent - ); - } else { - println!( - " • {} ({}) - No orders yet", - stat.user_name, stat.user_city - ); - } - } - - // Join 4: Aggregated join - Category sales analysis - println!("\n--- Join 4: Sales by Product Category ---"); - - // Join orders with products to get category information - let order_products = JoinQuery::new(&orders, &products).inner_join( - Order::product_id_r(), - Product::id_r(), - |order, product| (order.clone(), product.clone()), - ); - - // Aggregate by category - let mut category_stats: HashMap, f64, std::collections::HashSet)> = - HashMap::new(); - - for (order, product) in &order_products { - let entry = category_stats - .entry(product.category.clone()) - .or_insert_with(|| (Vec::new(), 0.0, std::collections::HashSet::new())); - entry.0.push(order.id); - entry.1 += order.total; - entry.2.insert(order.user_id); - } - - let mut category_sales: Vec = category_stats - .into_iter() - .map(|(category, (orders, revenue, customers))| CategorySales { - category, - total_orders: orders.len(), - total_revenue: revenue, - unique_customers: customers.len(), - }) - .collect(); - - category_sales.sort_by(|a, b| { - b.total_revenue - .partial_cmp(&a.total_revenue) - .unwrap_or(std::cmp::Ordering::Equal) - }); - - for cs in &category_sales { - println!( - " • {}: {} orders - ${:.2} revenue - {} customers", - cs.category, cs.total_orders, cs.total_revenue, cs.unique_customers - ); - } - - // Join 5: Filtered join - High value orders - println!("\n--- Join 5: High Value Orders (>$100) with User Details ---"); - let high_value_orders = JoinQuery::new(&orders, &users).inner_join_where( - Order::user_id_r(), - User::id_r(), - |order, _user| order.total > 100.0, - |order, user| (user.name.clone(), order.id, order.total), - ); - - for (name, order_id, total) in &high_value_orders { - println!(" • {} - Order #{} - ${:.2}", name, order_id, total); - } - - // Join 6: Users in same city analysis - println!("\n--- Join 6: Users from Same City ---"); - let user_pairs = JoinQuery::new(&users, &users).inner_join_where( - User::city_r(), - User::city_r(), - |u1, u2| u1.id < u2.id, // Avoid duplicates and self-pairs - |u1, u2| (u1.name.clone(), u2.name.clone(), u1.city.clone()), - ); - - for (name1, name2, city) in &user_pairs { - println!(" • {} and {} both live in {}", name1, name2, city); - } - - // Join 7: Product popularity - println!("\n--- Join 7: Product Popularity Ranking ---"); - - // Join orders with products - let product_order_pairs = JoinQuery::new(&products, &orders).inner_join( - Product::id_r(), - Order::product_id_r(), - |product, order| (product.clone(), order.clone()), - ); - - // Aggregate by product - let mut product_sales: HashMap = HashMap::new(); - for (product, order) in &product_order_pairs { - let entry = product_sales - .entry(product.id) - .or_insert_with(|| (product.name.clone(), 0, 0, 0.0)); - entry.1 += 1; // order count - entry.2 += order.quantity; // total quantity - entry.3 += order.total; // total revenue - } - - let mut popularity: Vec<_> = product_sales.into_iter().collect(); - popularity.sort_by(|a, b| b.1.1.cmp(&a.1.1)); // sort by order count - - for (_, (name, order_count, total_qty, revenue)) in &popularity { - println!( - " • {} - {} orders - {} units - ${:.2}", - name, order_count, total_qty, revenue - ); - } - - // Join 8: User spending by city - println!("\n--- Join 8: Total Spending by City ---"); - - // Join users with orders to get city and spending info - let user_city_orders = JoinQuery::new(&users, &orders).inner_join( - User::id_r(), - Order::user_id_r(), - |user, order| (user.city.clone(), order.total, user.id), - ); - - // Aggregate by city - let mut city_spending: HashMap)> = HashMap::new(); - for (city, total, user_id) in &user_city_orders { - let entry = city_spending - .entry(city.clone()) - .or_insert_with(|| (0.0, std::collections::HashSet::new())); - entry.0 += total; - entry.1.insert(*user_id); - } - - let mut city_stats: Vec<_> = city_spending - .into_iter() - .map(|(city, (total, customers))| (city, total, customers.len())) - .collect(); - - city_stats.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); - - for (city, total, customer_count) in &city_stats { - println!( - " • {} - ${:.2} total - {} customers - ${:.2} avg", - city, - total, - customer_count, - total / *customer_count as f64 - ); - } - - // Statistics summary - println!("\n=== Summary Statistics ==="); - println!("Total orders: {}", orders.len()); - - let total_revenue: f64 = orders.iter().map(|o| o.total).sum(); - println!("Total revenue: ${:.2}", total_revenue); - println!( - "Average order value: ${:.2}", - total_revenue / orders.len() as f64 - ); - - // Count unique customers using a join - let unique_customers: std::collections::HashSet = - orders.iter().map(|o| o.user_id).collect(); - println!("Active customers: {}", unique_customers.len()); - println!( - "Average orders per customer: {:.1}", - orders.len() as f64 / unique_customers.len() as f64 - ); - - println!("\n✓ Join query builder demo complete!"); -} diff --git a/examples/keypath_enum_simple.rs b/examples/keypath_enum_simple.rs deleted file mode 100644 index 3665af9..0000000 --- a/examples/keypath_enum_simple.rs +++ /dev/null @@ -1,89 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -enum Status { - // Unit variant - Loading, - - // Single-field tuple variants - Success(String), - // not supported - Error(String), - // Error(Option), - // Data(Vec), - - // Multi-field tuple variant - Position(f64, f64), - - // Named field variant - User { name: String, age: Option }, -} - -fn main() { - println!("=== Enum Keypaths Access ==="); - - // Test unit variant - let loading = Status::Loading; - if let Some(status) = Status::loading_r().get(&loading) { - println!("Status: {:?}", status); - } - - // Test single-field tuple variants - let success = Status::Success("Operation completed".to_string()); - if let Some(message) = Status::success_r().get(&success) { - println!("Success message: {}", message); - } - - let error = Status::Error("Something went wrong".to_string()); - if let Some(error_msg) = Status::error_r().get(&error) { - println!("Error message: {}", error_msg); - } - - // let data = Status::Data(vec![1, 2, 3, 4, 5]); - // if let Some(first_value) = Status::data().get(&data) { - // println!("First data value: {}", first_value); - // } - - // Test multi-field tuple variant - let position = Status::Position(10.5, 20.3); - if let Some(pos) = Status::position_f0_r().get(&position) { - println!("Position: {:?}", pos); - } - - // Test named field variant - let user = Status::User { - name: "Akash".to_string(), - age: Some(30), - }; - if let Some(user_status_name) = Status::user_name_r().get(&user) { - println!("User status name : {:?}", user_status_name); - } - - // Test non-matching variants - let loading_status = Status::Loading; - if let Some(message) = Status::success_r().get(&loading_status) { - println!("This should not print: {}", message); - } else { - println!("✓ Correctly returned None for non-matching variant"); - } - - println!("\n=== Keypaths Types ==="); - println!( - "loading() returns: KeyPath Fn(&\'r Status) -> &\'r Status> (readable)" - ); - println!( - "success() returns: KeyPath Fn(&\'r Status) -> &\'r String> (failable readable)" - ); - println!( - "error() returns: KeyPath Fn(&\'r Status) -> &\'r String> (failable readable)" - ); - println!( - "data() returns: KeyPath Fn(&\'r Status) -> &\'r i32> (failable readable)" - ); - println!( - "position() returns: KeyPath Fn(&\'r Status) -> &\'r Status> (failable readable)" - ); - println!( - "user() returns: KeyPath Fn(&\'r Status) -> &\'r Status> (failable readable)" - ); -} diff --git a/examples/keypath_enum_test.rs b/examples/keypath_enum_test.rs deleted file mode 100644 index 50e22ea..0000000 --- a/examples/keypath_enum_test.rs +++ /dev/null @@ -1,172 +0,0 @@ -use keypaths_proc::Keypath; -use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; -use std::rc::Rc; -use std::sync::Arc; - -// todo add support for more cointainers for Keypaths macro -#[derive(Debug, Keypath)] -enum Message { - // Unit variant - Ping, - - // Single-field tuple variants with different container types - Text(String), - Number(i32), - Email(Option), - Tags(Vec), - Metadata(HashMap), - Recipients(HashSet), - Queue(VecDeque), - Priority(BinaryHeap), - Content(Box), - Reference(Rc), - Shared(Arc>), - - // Multi-field tuple variant - Coordinate(f64, f64), - - // Named field variant - User { name: String, age: u32 }, -} - -fn main() { - println!("=== Enum Keypaths Access ==="); - - // Test unit variant - let ping = Message::Ping; - if let Some(msg) = Message::ping().get(&ping) { - println!("Unit variant: {:?}", msg); - } - - // Test single-field tuple variants - let text_msg = Message::Text("Hello World".to_string()); - if let Some(text) = Message::text().get(&text_msg) { - println!("Text message: {}", text); - } - - let number_msg = Message::Number(42); - if let Some(num) = Message::number().get(&number_msg) { - println!("Number message: {}", num); - } - - let email_msg = Message::Email(Some("user@example.com".to_string())); - if let Some(email) = Message::email().get(&email_msg) { - println!("Email message: {}", email); - } - - let tags_msg = Message::Tags(vec!["urgent".to_string(), "important".to_string()]); - if let Some(tag) = Message::tags().get(&tags_msg) { - println!("First tag: {}", tag); - } - - let metadata_msg = Message::Metadata({ - let mut map = HashMap::new(); - map.insert("sender".to_string(), "akash".to_string()); - map.insert("timestamp".to_string(), "2024-01-01".to_string()); - map - }); - if let Some(metadata) = Message::metadata().get(&metadata_msg) { - println!("Metadata: {:?}", metadata); - } - - let recipients_msg = Message::Recipients({ - let mut set = HashSet::new(); - set.insert("bob".to_string()); - set.insert("charlie".to_string()); - set - }); - if let Some(recipient) = Message::recipients().get(&recipients_msg) { - println!("A recipient: {}", recipient); - } - - let queue_msg = Message::Queue({ - let mut deque = VecDeque::new(); - deque.push_back("task1".to_string()); - deque.push_back("task2".to_string()); - deque - }); - if let Some(task) = Message::queue().get(&queue_msg) { - println!("Front task: {}", task); - } - - let priority_msg = Message::Priority({ - let mut heap = BinaryHeap::new(); - heap.push(10); - heap.push(5); - heap.push(15); - heap - }); - if let Some(priority) = Message::priority().get(&priority_msg) { - println!("Peek priority: {}", priority); - } - - let content_msg = Message::Content(Box::new("Important content".to_string())); - if let Some(content) = Message::content().get(&content_msg) { - println!("Content: {}", content); - } - - let reference_msg = Message::Reference(Rc::new("Shared reference".to_string())); - if let Some(reference) = Message::reference().get(&reference_msg) { - println!("Reference: {}", reference); - } - - let shared_msg = Message::Shared(Arc::new({ - let mut map = HashMap::new(); - map.insert("key".to_string(), "value".to_string()); - map - })); - if let Some(shared) = Message::shared().get(&shared_msg) { - println!("Shared data keys: {:?}", shared.keys().collect::>()); - } - - // Test multi-field tuple variant - let coord_msg = Message::Coordinate(10.5, 20.3); - if let Some(coord) = Message::coordinate().get(&coord_msg) { - println!("Coordinate message: {:?}", coord); - } - - // Test named field variant - let user_msg = Message::User { - name: "Akash".to_string(), - age: 30, - }; - if let Some(user) = Message::user().get(&user_msg) { - println!("User message: {:?}", user); - } - - // Test non-matching variants - let ping_msg = Message::Ping; - if let Some(text) = Message::text().get(&ping_msg) { - println!("This should not print: {}", text); - } else { - println!("✓ Correctly returned None for non-matching variant"); - } - - println!("\n=== Enum Keypaths Types ==="); - println!( - "ping() returns: KeyPath Fn(&\'r Message) -> &\'r Message> (readable)" - ); - println!( - "text() returns: KeyPath Fn(&\'r Message) -> &\'r String> (failable readable)" - ); - println!( - "number() returns: KeyPath Fn(&\'r Message) -> &\'r i32> (failable readable)" - ); - println!( - "email() returns: KeyPath Fn(&\'r Message) -> &\'r String> (failable readable)" - ); - println!( - "tags() returns: KeyPath Fn(&\'r Message) -> &\'r String> (failable readable)" - ); - println!( - "metadata() returns: KeyPath Fn(&\'r Message) -> &\'r HashMap> (failable readable)" - ); - println!( - "coordinate() returns: KeyPath Fn(&\'r Message) -> &\'r Message> (failable readable)" - ); - println!( - "user() returns: KeyPath Fn(&\'r Message) -> &\'r Message> (failable readable)" - ); - - println!("\n=== All enum keypath tests completed successfully! ==="); -} diff --git a/examples/keypath_new_containers_test.rs b/examples/keypath_new_containers_test.rs deleted file mode 100644 index 33bc236..0000000 --- a/examples/keypath_new_containers_test.rs +++ /dev/null @@ -1,154 +0,0 @@ -use keypaths_proc::Kp; -use std::rc::Weak; -use std::sync::{Mutex, RwLock}; - -#[derive(Debug, Kp)] -struct ContainerTest { - // Error handling containers - result: Result, - result_int: Result, - - // Synchronization primitives - mutex_data: Mutex, - rwlock_data: RwLock, - - // Reference counting with weak references - weak_ref: Weak, - - // Basic types for comparison - name: String, - age: u32, -} - -fn main() { - println!("=== New Container Types Test ==="); - - let container = ContainerTest { - result: Ok("Success!".to_string()), - result_int: Ok(42), - mutex_data: Mutex::new("Mutex content".to_string()), - rwlock_data: RwLock::new(100), - weak_ref: Weak::new(), // Empty weak reference - name: "Akash".to_string(), - age: 30, - }; - - // Test Result - returns Ok value if available - if let Some(value) = ContainerTest::result_fr().get(&container) { - println!("Result value: {}", value); - } else { - println!("Result is Err or None"); - } - - if let Some(value) = ContainerTest::result_int_fr().get(&container) { - println!("Result int value: {}", value); - } else { - println!("Result int is Err or None"); - } - - // Test Mutex - returns reference to the Mutex container - let mutex_ref = ContainerTest::mutex_data_r().get(&container); - println!("Mutex reference: {:?}", mutex_ref); - // To access the inner data, you would need to lock it manually - if let Ok(data) = mutex_ref.try_lock() { - println!("Mutex data: {}", *data); - } else { - println!("Mutex is locked"); - } - - // Test RwLock - returns reference to the RwLock container - let rwlock_ref = ContainerTest::rwlock_data_r().get(&container); - println!("RwLock reference: {:?}", rwlock_ref); - // To access the inner data, you would need to lock it manually - if let Ok(data) = rwlock_ref.try_read() { - println!("RwLock data: {}", *data); - } else { - println!("RwLock is locked"); - } - - // Test Weak - returns reference to the Weak container - // Note: Weak::new() creates an empty weak reference with no associated strong reference. - // Since there's no Rc or Arc backing it, .upgrade() returns None. - // To see a successful upgrade, create the Weak from an Rc or Arc: - // let rc = Rc::new("Shared reference".to_string()); - // let weak_ref = Rc::downgrade(&rc); // Create Weak from Rc - let weak_ref = ContainerTest::weak_ref_r().get(&container); - println!("Weak reference: {:?}", weak_ref); - // To access the inner data, you would need to upgrade it manually - if let Some(rc) = weak_ref.upgrade() { - println!("Weak ref upgraded to: {}", *rc); - } else { - println!("Weak ref upgrade failed (expected - Weak::new() creates empty reference)"); - } - - // Test basic types for comparison - let name = ContainerTest::name_r().get(&container); - println!("Name: {}", name); - - let age = ContainerTest::age_r().get(&container); - println!("Age: {}", age); - - // Test with error cases - println!("\n=== Error Cases ==="); - - let error_container = ContainerTest { - result: Err("Something went wrong".to_string()), - result_int: Err("Invalid number".to_string()), - mutex_data: Mutex::new("Error content".to_string()), - rwlock_data: RwLock::new(200), - weak_ref: Weak::new(), - name: "Bob".to_string(), - age: 25, - }; - - // Result with error should return None - if let Some(value) = ContainerTest::result_fr().get(&error_container) { - println!("This should not print: {}", value); - } else { - println!("✓ Correctly returned None for Err result"); - } - - if let Some(value) = ContainerTest::result_int_fr().get(&error_container) { - println!("This should not print: {}", value); - } else { - println!("✓ Correctly returned None for Err result_int"); - } - - // Mutex and RwLock should still work - let mutex_ref = ContainerTest::mutex_data_r().get(&error_container); - println!("Error container mutex reference: {:?}", mutex_ref); - if let Ok(data) = mutex_ref.try_lock() { - println!("Error container mutex data: {}", *data); - } - - let rwlock_ref = ContainerTest::rwlock_data_r().get(&error_container); - println!("Error container rwlock reference: {:?}", rwlock_ref); - if let Ok(data) = rwlock_ref.try_read() { - println!("Error container rwlock data: {}", *data); - } - - println!("\n=== Keypaths Types ==="); - println!( - "result() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r String> (failable readable)" - ); - println!( - "result_int() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r i32> (failable readable)" - ); - println!( - "mutex_data() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r Mutex> (readable)" - ); - println!( - "rwlock_data() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r RwLock> (readable)" - ); - println!( - "weak_ref() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r Weak> (readable)" - ); - println!( - "name() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r String> (readable)" - ); - println!( - "age() returns: KeyPath Fn(&\'r ContainerTest) -> &\'r u32> (readable)" - ); - - println!("\n=== All new container tests completed successfully! ==="); -} diff --git a/examples/keypath_simple.rs b/examples/keypath_simple.rs deleted file mode 100644 index 4ecd2cb..0000000 --- a/examples/keypath_simple.rs +++ /dev/null @@ -1,62 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -struct Person { - name: String, - age: u32, - email: Option, - hobbies: Vec, - scores: std::collections::HashMap, -} - -fn main() { - let person = Person { - name: "John Doe".to_string(), - age: 25, - email: Some("john@example.com".to_string()), - hobbies: vec!["reading".to_string(), "coding".to_string()], - scores: { - let mut map = std::collections::HashMap::new(); - map.insert("math".to_string(), 95); - map.insert("science".to_string(), 88); - map - }, - }; - - println!("=== Smart Keypaths Access ==="); - - // Basic types - readable keypath - println!("Name: {:?}", Person::name_r().get(&person)); - println!("Age: {:?}", Person::age_r().get(&person)); - - // Option - failable readable keypath to inner type - if let Some(email) = Person::email_fr().get(&person) { - println!("Email: {}", email); - } - - // Vec - failable readable keypath to first element - if let Some(hobby) = Person::hobbies_fr().get(&person) { - println!("First hobby: {}", hobby); - } - - // HashMap - readable keypath to container (returns &HashMap directly, not Option) - let scores = Person::scores_r().get(&person); - println!("Scores: {:?}", scores); - - println!("\n=== Keypaths Types ==="); - println!( - "name() returns: KeyPath Fn(&\'r Person) -> &\'r String> (readable)" - ); - println!( - "age() returns: KeyPath Fn(&\'r Person) -> &\'r u32> (readable)" - ); - println!( - "email() returns: KeyPath Fn(&\'r Person) -> &\'r String> (failable readable)" - ); - println!( - "hobbies() returns: KeyPath Fn(&\'r Person) -> &\'r String> (failable readable)" - ); - println!( - "scores() returns: KeyPath Fn(&\'r Person) -> &\'r HashMap> (readable)" - ); -} diff --git a/examples/keypath_test.rs b/examples/keypath_test.rs deleted file mode 100644 index 2e78d00..0000000 --- a/examples/keypath_test.rs +++ /dev/null @@ -1,167 +0,0 @@ -use keypaths_proc::Keypath; -use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; -use std::rc::Rc; -use std::sync::Arc; - -#[derive(Debug, Keypath)] -struct User { - name: String, - age: u32, - email: Option, - tags: Vec, - preferences: HashMap, - friends: HashSet, - scores: BTreeMap, - history: VecDeque, - notes: LinkedList, - priority_queue: BinaryHeap, - profile: Box, - avatar: Rc, - metadata: Arc>, -} - -#[derive(Debug)] -struct UserProfile { - bio: String, - location: String, -} - -#[derive(Debug, Keypath)] -struct TupleStruct(String, Option, Vec); - -fn main() { - let user = User { - name: "Akash".to_string(), - age: 30, - email: Some("akash@example.com".to_string()), - tags: vec!["developer".to_string(), "rust".to_string()], - preferences: { - let mut map = HashMap::new(); - map.insert("theme".to_string(), "dark".to_string()); - map.insert("language".to_string(), "en".to_string()); - map - }, - friends: { - let mut set = HashSet::new(); - set.insert("bob".to_string()); - set.insert("charlie".to_string()); - set - }, - scores: { - let mut map = BTreeMap::new(); - map.insert("math".to_string(), 95); - map.insert("science".to_string(), 88); - map - }, - history: { - let mut deque = VecDeque::new(); - deque.push_back("login".to_string()); - deque.push_back("view_profile".to_string()); - deque - }, - notes: { - let mut list = LinkedList::new(); - list.push_back("Important note".to_string()); - list.push_back("Another note".to_string()); - list - }, - priority_queue: { - let mut heap = BinaryHeap::new(); - heap.push(10); - heap.push(5); - heap.push(15); - heap - }, - profile: Box::new(UserProfile { - bio: "Software developer".to_string(), - location: "San Francisco".to_string(), - }), - avatar: Rc::new("avatar.png".to_string()), - metadata: Arc::new({ - let mut map = HashMap::new(); - map.insert("created_at".to_string(), "2024-01-01".to_string()); - map - }), - }; - - println!("=== Smart Keypaths Access ==="); - - // Basic types - readable keypath - println!("Name: {:?}", User::name().get(&user)); - println!("Age: {:?}", User::age().get(&user)); - - // Option - failable readable keypath to inner type - if let Some(email) = User::email().get(&user) { - println!("Email: {}", email); - } else { - println!("No email found"); - } - - // Vec - failable readable keypath to first element - if let Some(tag) = User::tags().get(&user) { - println!("First tag: {}", tag); - } - - // HashMap - readable keypath to container - if let Some(preferences) = User::preferences().get(&user) { - println!("Preferences: {:?}", preferences); - } - - // HashSet - failable readable keypath to any element - if let Some(friend) = User::friends().get(&user) { - println!("A friend: {}", friend); - } - - // BTreeMap - readable keypath to container - if let Some(scores) = User::scores().get(&user) { - println!("Scores: {:?}", scores); - } - - // VecDeque - failable readable keypath to front element - if let Some(history) = User::history().get(&user) { - println!("Front history: {}", history); - } - - // LinkedList - failable readable keypath to front element - if let Some(note) = User::notes().get(&user) { - println!("Front note: {}", note); - } - - // BinaryHeap - failable readable keypath to peek element - if let Some(priority) = User::priority_queue().get(&user) { - println!("Peek priority: {}", priority); - } - - // Box - readable keypath to inner type - if let Some(profile) = User::profile().get(&user) { - println!("Profile bio: {}", profile.bio); - } - - // Rc - readable keypath to inner type - if let Some(avatar) = User::avatar().get(&user) { - println!("Avatar: {}", avatar); - } - - // Arc - readable keypath to inner type - if let Some(metadata) = User::metadata().get(&user) { - println!("Metadata keys: {:?}", metadata.keys().collect::>()); - } - - // Test tuple struct - println!("\n=== Tuple Struct ==="); - let tuple = TupleStruct("test".to_string(), Some(42), vec![1.0, 2.0, 3.0]); - - if let Some(f0) = TupleStruct::f0().get(&tuple) { - println!("Tuple f0: {}", f0); - } - - if let Some(f1) = TupleStruct::f1().get(&tuple) { - println!("Tuple f1 (Option): {}", f1); - } - - if let Some(f2) = TupleStruct::f2().get(&tuple) { - println!("Tuple f2 (Vec first): {}", f2); - } - - println!("\n=== All smart keypath tests completed successfully! ==="); -} diff --git a/examples/keypaths_new_containers_test.rs b/examples/keypaths_new_containers_test.rs deleted file mode 100644 index bc10986..0000000 --- a/examples/keypaths_new_containers_test.rs +++ /dev/null @@ -1,61 +0,0 @@ -use keypaths_proc::Kp; -use std::rc::Weak; -use std::sync::{Mutex, RwLock}; - -#[derive(Debug, Kp)] -struct ContainerTest { - // Error handling containers - result: Result, - result_int: Result, - - // Synchronization primitives - mutex_data: Mutex, - rwlock_data: RwLock, - - // Reference counting with weak references - weak_ref: Weak, - - // Basic types for comparison - name: String, - age: u32, -} - -fn main() { - println!("=== Keypaths Macro New Container Types Test ==="); - - let container = ContainerTest { - result: Ok("Success!".to_string()), - result_int: Ok(42), - mutex_data: Mutex::new("Mutex content".to_string()), - rwlock_data: RwLock::new(100), - weak_ref: Weak::new(), - name: "Akash".to_string(), - age: 30, - }; - - // Test Result with Keypaths - if let Some(value) = ContainerTest::result_fr().get(&container) { - println!("✅ Result value: {}", value); - } - - // Test Mutex with Keypaths - let mutex_ref = ContainerTest::mutex_data_r().get(&container); - println!("✅ Mutex reference: {:?}", mutex_ref); - - // Test RwLock with Keypaths - let rwlock_ref = ContainerTest::rwlock_data_r().get(&container); - println!("✅ RwLock reference: {:?}", rwlock_ref); - - // Test Weak with Keypaths - let weak_ref = ContainerTest::weak_ref_r().get(&container); - println!("✅ Weak reference: {:?}", weak_ref); - - // Test basic types - let name = ContainerTest::name_r().get(&container); - println!("✅ Name: {}", name); - - let age = ContainerTest::age_r().get(&container); - println!("✅ Age: {}", age); - - println!("\n=== Keypaths Macro - All new container types supported! ==="); -} diff --git a/examples/kp_derive_showcase.rs b/examples/kp_derive_showcase.rs deleted file mode 100644 index 875d380..0000000 --- a/examples/kp_derive_showcase.rs +++ /dev/null @@ -1,165 +0,0 @@ -// Example demonstrating the extended Kp derive macro with all wrapper types -use key_paths_derive::Kp; -use std::collections::HashMap; -use std::sync::Arc; - -#[derive(Kp, Debug, Clone)] -struct Address { - street: String, - city: String, - zip: String, -} - -#[derive(Kp, Debug)] -struct Person { - name: String, - age: i32, - email: Option, - address: Box
, -} - -#[derive(Kp, Debug)] -struct Company { - name: String, - employees: Vec, - shared_resources: Arc, - metadata: HashMap, -} - -fn main() { - // Create sample data - let company = Company { - name: "Tech Corp".to_string(), - employees: vec![ - Person { - name: "Akash".to_string(), - age: 30, - email: Some("alice@example.com".to_string()), - address: Box::new(Address { - street: "123 Main St".to_string(), - city: "New York".to_string(), - zip: "10001".to_string(), - }), - }, - Person { - name: "Bob".to_string(), - age: 25, - email: None, - address: Box::new(Address { - street: "456 Oak Ave".to_string(), - city: "San Francisco".to_string(), - zip: "94102".to_string(), - }), - }, - ], - shared_resources: Arc::new("Shared Company Data".to_string()), - metadata: { - let mut map = HashMap::new(); - map.insert("founded".to_string(), "2020".to_string()); - map.insert("industry".to_string(), "Technology".to_string()); - map - }, - }; - - println!("=== Kp Derive Macro Examples ===\n"); - - // Example 1: Basic field access - println!("1. Basic Field Access:"); - let company_name_kp = Company::name(); - if let Some(name) = company_name_kp.get(&company) { - println!(" Company name: {}", name); - } - - // Example 2: Vec access (gets first element) - println!("\n2. Vec Access (first element):"); - let employees_kp = Company::employees(); - if let Some(first_employee) = employees_kp.get(&company) { - println!(" First employee: {}", first_employee.name); - } - - // Example 3: Option unwrapping - println!("\n3. Option Unwrapping:"); - let employees_kp = Company::employees(); - - if let Some(first_employee) = employees_kp.get(&company) { - let email_kp = Person::email(); - if let Some(email) = email_kp.get(first_employee) { - println!(" First employee email: {}", email); - } else { - println!(" First employee has no email"); - } - } - - // Example 4: Box dereferencing - println!("\n4. Box Dereferencing:"); - let employees_kp = Company::employees(); - - if let Some(first_employee) = employees_kp.get(&company) { - let address_kp = Person::address(); - if let Some(address) = address_kp.get(first_employee) { - println!( - " First employee's address: {} {}, {}", - address.street, address.city, address.zip - ); - } - } - - // Example 5: Arc access - println!("\n5. Arc Access:"); - let resources_kp = Company::shared_resources(); - if let Some(resources) = resources_kp.get(&company) { - println!(" Shared resources: {}", resources); - } - - // Example 6: HashMap access - println!("\n6. HashMap Access:"); - let metadata_kp = Company::metadata(); - if let Some(metadata) = metadata_kp.get(&company) { - println!(" Metadata: {:?}", metadata); - } - - // Example 7: Mutable access - println!("\n7. Mutable Access:"); - let mut mutable_person = Person { - name: "Charlie".to_string(), - age: 35, - email: Some("charlie@example.com".to_string()), - address: Box::new(Address { - street: "789 Pine Rd".to_string(), - city: "Seattle".to_string(), - zip: "98101".to_string(), - }), - }; - - println!(" Before: age = {}", mutable_person.age); - - let age_kp = Person::age(); - age_kp.get_mut(&mut mutable_person).map(|age| *age = 36); - - println!(" After: age = {}", mutable_person.age); - - // Example 8: Modifying Option inner value - println!("\n8. Modifying Option Inner Value:"); - println!(" Before: email = {:?}", mutable_person.email); - - let email_kp = Person::email(); - email_kp - .get_mut(&mut mutable_person) - .map(|email| *email = "newemail@example.com".to_string()); - - println!(" After: email = {:?}", mutable_person.email); - - // Example 9: Modifying Box inner value - println!("\n9. Modifying Box Inner Value:"); - - println!(" Before: city = {}", mutable_person.address.city); - - let address_kp = Person::address(); - if let Some(address) = address_kp.get_mut(&mut mutable_person) { - address.city = "Portland".to_string(); - } - - println!(" After: city = {}", mutable_person.address.city); - - println!("\n=== All Examples Complete ==="); -} diff --git a/examples/kp_pkp_wgpu.rs b/examples/kp_pkp_wgpu.rs deleted file mode 100644 index 33e5811..0000000 --- a/examples/kp_pkp_wgpu.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Example: wgpu runner using **Kp** and **PKp** with the derive macro and a functional API. -//! -//! - Uses `#[derive(Kp, Pkp, Akp)]`; keypaths come from `User::score()` / `User::name()`. -//! - Numeric tier is built with [IntoNumericAKp]: `User::score().into_numeric_akp(wgsl)` (reference-based, no clone of root). -//! - Uses [Kp::map] for a derived keypath: e.g. `score_kp.map(|s: &f32| *s)` or a mapped view; arbitrary tier uses `AKp::new(name_kp)`. -//! -//! Run with: `cargo run --example kp_pkp_wgpu` - -use key_paths_derive::{Akp, Kp, Pkp}; -use key_paths_iter::wgpu::{AKpRunner, AKpTier, GpuValue, IntoNumericAKp, RunResults, WgpuContext}; -use rust_key_paths::{AKp, KpType}; - -#[derive(Kp, Pkp, Akp, Debug)] -struct User { - name: String, - score: f32, -} - -fn main() -> Result<(), Box> { - let user = User { - 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")); - - // Functional API: Kp::map takes a reference (no copy of value in the get path) - let score_kp = User::score(); - let doubled = score_kp.map(|s: &f32| *s * 2.0); - let _ = doubled.get(&user); // Some(84.0) - - // Arbitrary: Kp from derive → AKp - let name_kp: KpType<'static, User, String> = User::name(); - let name_tier = AKpTier::Arbitrary(AKp::new(name_kp)); - - let wgpu_ctx = WgpuContext::new().ok(); - let runner = AKpRunner::new(vec![score_tier, name_tier], wgpu_ctx); - - let results: RunResults = runner.run(&user as &dyn std::any::Any); - - println!("Numeric (GPU or CPU fallback):"); - for (i, v) in results.numeric.iter().enumerate() { - match v { - Some(GpuValue::F32(f)) => println!(" [{}] f32 = {}", i, f), - Some(GpuValue::U32(u)) => println!(" [{}] u32 = {}", i, u), - None => println!(" [{}] (none)", i), - } - } - println!("Arbitrary KPs run (CPU): {}", results.arbitrary_count); - - if let Some(Some(GpuValue::F32(f))) = results.numeric.first() { - assert!((*f - 85.0).abs() < 1e-5, "expected 85.0, got {}", f); - } - - Ok(()) -} diff --git a/examples/kp_zip_example.rs b/examples/kp_zip_example.rs deleted file mode 100644 index 620577c..0000000 --- a/examples/kp_zip_example.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! 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/lock_keypaths_conversion.rs b/examples/lock_keypaths_conversion.rs deleted file mode 100644 index 8c162ae..0000000 --- a/examples/lock_keypaths_conversion.rs +++ /dev/null @@ -1,44 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::KeyPath; -use std::sync::{Arc, RwLock}; - -#[derive(Kp)] -#[All] -struct SomeStruct { - data: String, -} - -#[derive(Kp)] -#[All] -struct Container { - rwlock_data: Arc>, -} - -fn main() { - let container = Container { - rwlock_data: Arc::new(RwLock::new(SomeStruct { - data: "test".to_string(), - })), - }; - - // Using the new to_arc_rwlock_kp() method directly on keypaths - println!("=== Using to_arc_rwlock_kp() method ==="); - - // Convert a normal keypath to a lock keypath using the method - Container::rwlock_data_r() - .to_arc_rwlock_kp() - .chain_arc_rwlock_at_kp(SomeStruct::data_r()) - .get(&container, |value| { - println!("✅ Read value via to_arc_rwlock_kp(): {}", value); - }); - - // Direct usage (without conversion method) - still works - println!("\n=== Direct usage (without conversion) ==="); - Container::rwlock_data_r() - .chain_arc_rwlock_at_kp(SomeStruct::data_r()) - .get(&container, |value| { - println!("✅ Read value directly: {}", value); - }); - - println!("\n✅ to_arc_rwlock_kp() method is working correctly!"); -} diff --git a/examples/minimal_test.rs b/examples/minimal_test.rs deleted file mode 100644 index bad07c8..0000000 --- a/examples/minimal_test.rs +++ /dev/null @@ -1,10 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -struct MinimalTest { - box_option_field: Box>, -} - -fn main() { - println!("Minimal test"); -} diff --git a/examples/nested_with_options.rs b/examples/nested_with_options.rs deleted file mode 100644 index 347e6cc..0000000 --- a/examples/nested_with_options.rs +++ /dev/null @@ -1,220 +0,0 @@ -use keypaths_proc::Kp; -use std::sync::Arc; - -#[derive(Debug, Clone, Kp)] -struct SomeStruct { - value: Option, -} - -// Example struct demonstrating all nested container combinations -#[derive(Debug, Clone, Kp)] -#[All] -struct NestedContainerExample { - // Option> - option_box_field: Option>, - // Option> - option_rc_field: Option>, - // Option> - option_arc_field: Option>, - // Box> - box_option_field: Box>, - // Rc> - rc_option_field: std::rc::Rc>, - // Arc> - arc_option_field: std::sync::Arc>, - // // Vec> - // vec_option_field: Vec>, - // // Option> - // option_vec_field: Option>, - // // HashMap> - // hashmap_option_field: HashMap>, - // // Option> - // option_hashmap_field: Option>, - // // Support added for even random containers not even possible in real life - value: Option>>, -} - -fn main() { - println!("=== Nested Container Options Example ===\n"); - - let mut example = NestedContainerExample { - value: Some(Arc::new(Box::new(SomeStruct { - value: Some(String::from("Hello, world!")), - }))), - option_box_field: Some(Box::new("jkhkhjhk".to_string())), - option_rc_field: Some(std::rc::Rc::new("World".to_string())), - option_arc_field: Some(std::sync::Arc::new("Rust".to_string())), - box_option_field: Box::new(Some(42)), - rc_option_field: std::rc::Rc::new(Some(100)), - arc_option_field: std::sync::Arc::new(Some(200)), - // vec_option_field: vec![Some(3.14), None, Some(2.71)], - // option_vec_field: Some(vec![true, false, true]), - // hashmap_option_field: { - // let mut map = HashMap::new(); - // map.insert("key1".to_string(), Some(10)); - // map.insert("key2".to_string(), None); - // map.insert("key3".to_string(), Some(20)); - // map - // }, - // option_hashmap_field: { - // let mut map = HashMap::new(); - // map.insert("a".to_string(), 1); - // map.insert("b".to_string(), 2); - // Some(map) - // }, - }; - println!("Value"); - if let Some(value) = NestedContainerExample::value_fr() - .for_box() - .then(SomeStruct::value_fr()) - .get(&example) - { - // *value = String::from("changed"); - println!(" Changed value: {:?}", value); - } - - // Test Option> - println!("1. Option>:"); - if let Some(value) = NestedContainerExample::option_box_field_fr().get(&example) { - println!(" Read value: {}", value); - } - - if let Some(value) = NestedContainerExample::option_box_field_fw().get_mut(&mut example) { - *value = "kjlkjljljk".to_string(); - println!(" Changed value: {}", value); - } - println!(); - - let x = NestedContainerExample::option_rc_field_fr(); - // crate::NestedContainerExample:: - // Test Option> - println!("2. Option>:"); - if let Some(value) = NestedContainerExample::option_rc_field_fr().get(&example) { - println!(" Read value: {}", value); - } - println!(); - - // Test Option> - println!("3. Option>:"); - if let Some(value) = NestedContainerExample::option_arc_field_fr().get(&example) { - println!(" Read value: {}", value); - } - println!(); - - // Test Box> - println!("4. Box>:"); - // Read the inner i32 if Some - if let Some(value) = NestedContainerExample::box_option_field_fr().get(&example) { - println!(" Inner value: {}", value); - } - - if let Some(value) = NestedContainerExample::box_option_field_fw().get_mut(&mut example) { - *value = 99; - println!(" Changed inner value: {}", value); - } - println!(); - - // Test Rc> - println!("5. Rc>:"); - if let Some(value) = NestedContainerExample::rc_option_field_fr().get(&example) { - println!(" Inner value: {}", value); - } - println!(); - - // Test Arc> - println!("6. Arc>:"); - if let Some(value) = NestedContainerExample::arc_option_field_fr().get(&example) { - println!(" Inner value: {}", value); - } - println!(); - // - // // Test Vec> - // println!("7. Vec>:"); - // for (i, val) in example.vec_option_field.iter().enumerate() { - // println!(" Index {}: {:?}", i, val); - // } - // - // if let Some(value) = NestedContainerExample::vec_option_field_fr().get(&example) { - // println!(" First value: {}", value); - // } - // - // if let Some(value) = NestedContainerExample::vec_option_field_fr_at(0).get(&example) { - // println!(" Value at index 0: {}", value); - // } - // - // if let Some(value) = NestedContainerExample::vec_option_field_fw_at(2).get_mut(&mut example) { - // *value = 1.41; - // println!(" Changed value at index 2: {}", value); - // } - // println!(); - // - // // Test Option> - // println!("8. Option>:"); - // if let Some(value) = NestedContainerExample::option_vec_field_fr().get(&example) { - // println!(" First value: {}", value); - // } - // - // if let Some(value) = NestedContainerExample::option_vec_field_fw().get_mut(&mut example) { - // *value = false; - // println!(" Changed value: {}", value); - // } - // - // if let Some(value) = NestedContainerExample::option_vec_field_fr_at(1).get(&example) { - // println!(" Value at index 1: {}", value); - // } - // println!(); - // - // // Test HashMap> - // println!("9. HashMap>:"); - // for (key, val) in example.hashmap_option_field.iter() { - // println!(" {}: {:?}", key, val); - // } - // - // let key = "key1".to_string(); - // // fr returns the inner usize (unwrap Option inside HashMap) - // if let Some(value) = NestedContainerExample::hashmap_option_field_fr(key.clone()).get(&example) { - // println!(" Value for 'key1': {}", value); - // } - // - // if let Some(value) = NestedContainerExample::hashmap_option_field_fw(key).get_mut(&mut example) { - // *value = 50; - // println!(" Changed value for 'key1': {}", value); - // } - // println!(); - // - // // Test Option> - // println!("10. Option>:"); - // for (key, val) in example.option_hashmap_field.as_ref().unwrap().iter() { - // println!(" {}: {:?}", key, val); - // } - // - // let key = "a".to_string(); - // if let Some(value) = NestedContainerExample::option_hashmap_field_fr(key.clone()).get(&example) { - // println!(" Value for 'a': {}", value); - // } - // - // if let Some(value) = NestedContainerExample::option_hashmap_field_fw(key).get_mut(&mut example) { - // *value = 100; - // println!(" Changed value for 'a': {}", value); - // } - // println!(); - - // Demonstrate composition - println!("=== Composition Example ==="); - - // Compose Option> with another struct field - #[derive(Debug, Kp)] - struct Outer { - inner: Option>, - } - - let mut outer = Outer { - inner: Some(Box::new(example.clone())), - }; - - // This would work when we have methods available - // let path = Outer::inner_fr().then(NestedContainerExample::option_box_field_fr()); - println!("Composition ready for outer structures"); - - println!("\n=== All tests completed successfully! ==="); -} diff --git a/examples/no_clone_mutex_rwlock_example.rs b/examples/no_clone_mutex_rwlock_example.rs deleted file mode 100644 index 675e9f8..0000000 --- a/examples/no_clone_mutex_rwlock_example.rs +++ /dev/null @@ -1,192 +0,0 @@ -// Example demonstrating the no-clone approach for Mutex and RwLock with KeyPaths -// Run with: cargo run --example no_clone_mutex_rwlock_example - -use rust_keypaths::{ - KeyPath, OptionalKeyPath, WithContainer, WritableKeyPath, WritableOptionalKeyPath, -}; -use std::sync::{Mutex, RwLock}; - -#[derive(Debug, Clone)] -struct User { - name: String, - age: u32, - email: Option, -} - -fn main() { - println!("=== No-Clone Mutex and RwLock Example ===\n"); - - // Create some 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()); - - // ===== Example 1: Basic Mutex Usage (No Clone) ===== - println!("--- Example 1: Basic Mutex Usage (No Clone) ---"); - - let mutex_user = Mutex::new(user.clone()); - - // Access data from Mutex using with_mutex() - no cloning! - if let Some(name) = name_path - .clone() - .with_mutex(&mutex_user, |name| name.clone()) - { - println!(" Name from Mutex: {}", name); - } - - // Or just print directly without cloning - name_path.clone().with_mutex(&mutex_user, |name| { - println!(" Name from Mutex (direct): {}", name); - }); - - // ===== Example 2: Basic RwLock Usage (No Clone) ===== - println!("--- Example 2: Basic RwLock Usage (No Clone) ---"); - - let rwlock_user = RwLock::new(user.clone()); - - // Access data from RwLock using with_rwlock() - no cloning! - name_path.clone().with_rwlock(&rwlock_user, |name| { - println!(" Name from RwLock: {}", name); - }); - - // ===== Example 3: Mutex with Failable KeyPath (No Clone) ===== - println!("--- Example 3: Mutex with Failable KeyPath (No Clone) ---"); - - let mutex_user_with_email = Mutex::new(User { - name: "Bob".to_string(), - age: 25, - email: Some("bob@example.com".to_string()), - }); - - // Access optional email from Mutex - no cloning! - email_path - .clone() - .with_mutex(&mutex_user_with_email, |email| { - println!(" Email from Mutex: {}", email); - }); - - // ===== Example 4: RwLock with Failable KeyPath (No Clone) ===== - println!("--- Example 4: RwLock with Failable KeyPath (No Clone) ---"); - - let rwlock_user_with_email = RwLock::new(User { - name: "Charlie".to_string(), - age: 35, - email: None, // No email - }); - - // Access optional email from RwLock (should return None) - if email_path - .clone() - .with_rwlock(&rwlock_user_with_email, |email| email.clone()) - .is_some() - { - println!(" Email found in RwLock"); - } else { - println!(" No email found in RwLock (as expected)"); - } - - // ===== Example 5: Collection Processing (No Clone) ===== - println!("--- Example 5: Collection Processing (No Clone) ---"); - - let mutex_users: Vec> = vec![ - Mutex::new(User { - name: "David".to_string(), - age: 28, - email: Some("david@example.com".to_string()), - }), - Mutex::new(User { - name: "Eve".to_string(), - age: 32, - email: None, - }), - Mutex::new(User { - name: "Frank".to_string(), - age: 45, - email: Some("frank@example.com".to_string()), - }), - ]; - - // Process names from Mutex collection - no cloning! - let mut names = Vec::new(); - for mutex_user in &mutex_users { - name_path.clone().with_mutex(mutex_user, |name| { - names.push(name.clone()); // Only clone when we need to store - }); - } - println!(" User names: {:?}", names); - - // ===== Example 6: Mutable Access (No Clone) ===== - println!("--- Example 6: Mutable Access (No Clone) ---"); - - let mut mutex_user_mut = Mutex::new(User { - name: "Grace".to_string(), - age: 29, - email: Some("grace@example.com".to_string()), - }); - - // Modify data through Mutex - no cloning! - let name_path_w = WritableKeyPath::new(|u: &mut User| &mut u.name); - name_path_w - .clone() - .with_mutex_mut(&mut mutex_user_mut, |name| { - *name = "Grace Updated".to_string(); - println!(" Updated name to: {}", name); - }); - - // ===== Example 7: RwLock Mutable Access (No Clone) ===== - println!("--- Example 7: RwLock Mutable Access (No Clone) ---"); - - let mut rwlock_user_mut = RwLock::new(User { - name: "Henry".to_string(), - age: 38, - email: Some("henry@example.com".to_string()), - }); - - // Modify data through RwLock - no cloning! - let age_path_w = WritableKeyPath::new(|u: &mut User| &mut u.age); - age_path_w - .clone() - .with_rwlock_mut(&mut rwlock_user_mut, |age| { - *age += 1; - println!(" Updated age to: {}", age); - }); - - // ===== Example 8: Error Handling (No Clone) ===== - println!("--- Example 8: Error Handling (No Clone) ---"); - - // Create a Mutex that will be poisoned - let poisoned_mutex = Mutex::new(User { - name: "Poisoned".to_string(), - age: 99, - email: Some("poisoned@example.com".to_string()), - }); - - // Poison the mutex by panicking while holding the lock - { - let _guard = poisoned_mutex.lock().unwrap(); - std::panic::catch_unwind(|| { - panic!("This will poison the mutex"); - }) - .ok(); - } // _guard is dropped here - - // Try to access data from poisoned mutex (should return None) - if name_path - .clone() - .with_mutex(&poisoned_mutex, |name| name.clone()) - .is_some() - { - println!(" Successfully accessed poisoned Mutex"); - } else { - println!(" Failed to access poisoned Mutex (as expected)"); - } - - println!("=== All Examples Completed Successfully! ==="); -} diff --git a/examples/parking_lot_chains.rs b/examples/parking_lot_chains.rs deleted file mode 100644 index 6954977..0000000 --- a/examples/parking_lot_chains.rs +++ /dev/null @@ -1,416 +0,0 @@ -//! Example demonstrating parking_lot::Mutex and parking_lot::RwLock functional keypath chains -//! -//! Run with: cargo run --example parking_lot_chains --features parking_lot - -#[cfg(feature = "parking_lot")] -mod parking_lot_example { - use keypaths_proc::{Casepaths, Kp, WritableKp}; - use parking_lot::{Mutex as ParkingMutex, RwLock as ParkingRwLock}; - use rust_keypaths::{KeyPath, OptionalKeyPath, keypath, opt_keypath}; - use std::sync::Arc; - - // ========== Data Structures ========== - - #[derive(Debug)] - struct Container { - // Direct Arc fields (for KeyPath chains) - parking_mutex_data: Arc>, - parking_rwlock_data: Arc>, - // Optional Arc fields (for OptionalKeyPath chains) - optional_mutex: Option>>, - optional_rwlock: Option>>, - // Enum with Arc case - state: AppState, - } - - #[derive(Debug, Casepaths)] - enum AppState { - Idle, - Active(Arc>), - } - - #[derive(Debug, Kp, WritableKeypaths)] - struct Session { - user_name: String, - logged_in: bool, - } - - impl Container { - fn new() -> Self { - Self { - parking_mutex_data: Arc::new(ParkingMutex::new(DataStruct { - name: "Mutex Hello".to_string(), - optional_value: Some("Mutex Optional".to_string()), - count: 42, - })), - parking_rwlock_data: Arc::new(ParkingRwLock::new(DataStruct { - name: "RwLock Hello".to_string(), - optional_value: Some("RwLock Optional".to_string()), - count: 100, - })), - optional_mutex: Some(Arc::new(ParkingMutex::new(DataStruct { - name: "Optional Mutex".to_string(), - optional_value: Some("Opt Mutex Value".to_string()), - count: 1, - }))), - optional_rwlock: Some(Arc::new(ParkingRwLock::new(DataStruct { - name: "Optional RwLock".to_string(), - optional_value: Some("Opt RwLock Value".to_string()), - count: 2, - }))), - state: AppState::Active(Arc::new(ParkingRwLock::new(Session { - user_name: "Akash".to_string(), - logged_in: true, - }))), - } - } - - fn new_idle() -> Self { - let mut c = Self::new(); - c.state = AppState::Idle; - c - } - - // Manual keypath methods for Arc fields - fn parking_mutex_data_r() -> KeyPath< - Container, - Arc>, - impl for<'r> Fn(&'r Container) -> &'r Arc>, - > { - keypath!(|c: &Container| &c.parking_mutex_data) - } - - fn parking_rwlock_data_r() -> KeyPath< - Container, - Arc>, - impl for<'r> Fn(&'r Container) -> &'r Arc>, - > { - keypath!(|c: &Container| &c.parking_rwlock_data) - } - - // OptionalKeyPath methods for Option> fields - fn optional_mutex_fr() -> OptionalKeyPath< - Container, - Arc>, - impl for<'r> Fn(&'r Container) -> Option<&'r Arc>>, - > { - opt_keypath!(|c: &Container| c.optional_mutex.as_ref()) - } - - fn optional_rwlock_fr() -> OptionalKeyPath< - Container, - Arc>, - impl for<'r> Fn(&'r Container) -> Option<&'r Arc>>, - > { - opt_keypath!(|c: &Container| c.optional_rwlock.as_ref()) - } - - // OptionalKeyPath for enum -> Arc> chain - fn state_fr() -> OptionalKeyPath< - Container, - AppState, - impl for<'r> Fn(&'r Container) -> Option<&'r AppState>, - > { - opt_keypath!(|c: &Container| Some(&c.state)) - } - } - - #[derive(Debug, Kp, WritableKeypaths)] - struct DataStruct { - name: String, - optional_value: Option, - count: i32, - } - - pub fn run() { - println!("=== parking_lot Functional Keypath Chains Example ===\n"); - - let container = Container::new(); - - // ========== PART 1: KeyPath chains (direct Arc fields) ========== - println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - println!("PART 1: KeyPath chains (direct Arc fields)"); - println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); - - // ========== parking_lot::Mutex Examples ========== - println!("=== Arc> Chains ===\n"); - - // Example 1: Read through Arc> - Container::parking_mutex_data_r() - .chain_arc_parking_mutex_at_kp(DataStruct::name_r()) - .get(&container, |value| { - println!("✅ chain_arc_parking_mutex_at_kp (read): name = {}", value); - }); - - // Example 2: Read optional field through Arc> - Container::parking_mutex_data_r() - .then_arc_parking_mutex_optional_at_kp(DataStruct::optional_value_fr()) - .get(&container, |value| { - println!( - "✅ then_arc_parking_mutex_optional_at_kp (read): optional_value = {}", - value - ); - }); - - // Example 3: Write through Arc> - let write_container = Container::new(); - Container::parking_mutex_data_r() - .chain_arc_parking_mutex_writable_at_kp(DataStruct::name_w()) - .get_mut(&write_container, |value| { - *value = "Modified via chain_arc_parking_mutex_writable_at_kp".to_string(); - println!("✅ chain_arc_parking_mutex_writable_at_kp (write): Modified name"); - }); - - // Verify the write - Container::parking_mutex_data_r() - .chain_arc_parking_mutex_at_kp(DataStruct::name_r()) - .get(&write_container, |value| { - println!(" Verified: name = {}", value); - }); - - // Example 4: Write optional field through Arc> - Container::parking_mutex_data_r() - .then_arc_parking_mutex_writable_optional_at_kp(DataStruct::optional_value_fw()) - .get_mut(&write_container, |value| { - *value = "Modified optional via parking_mutex".to_string(); - println!("✅ then_arc_parking_mutex_writable_optional_at_kp (write): Modified optional_value"); - }); - - // Verify the write - Container::parking_mutex_data_r() - .then_arc_parking_mutex_optional_at_kp(DataStruct::optional_value_fr()) - .get(&write_container, |value| { - println!(" Verified: optional_value = {}", value); - }); - - // ========== parking_lot::RwLock Examples ========== - println!("\n=== Arc> Chains ===\n"); - - // Example 5: Read through Arc> - Container::parking_rwlock_data_r() - .chain_arc_parking_rwlock_at_kp(DataStruct::name_r()) - .get(&container, |value| { - println!("✅ chain_arc_parking_rwlock_at_kp (read): name = {}", value); - }); - - // Example 6: Read optional field through Arc> - Container::parking_rwlock_data_r() - .then_arc_parking_rwlock_optional_at_kp(DataStruct::optional_value_fr()) - .get(&container, |value| { - println!( - "✅ then_arc_parking_rwlock_optional_at_kp (read): optional_value = {}", - value - ); - }); - - // Example 7: Write through Arc> - let rwlock_write_container = Container::new(); - Container::parking_rwlock_data_r() - .chain_arc_parking_rwlock_writable_at_kp(DataStruct::name_w()) - .get_mut(&rwlock_write_container, |value| { - *value = "Modified via chain_arc_parking_rwlock_writable_at_kp".to_string(); - println!("✅ chain_arc_parking_rwlock_writable_at_kp (write): Modified name"); - }); - - // Verify the write - Container::parking_rwlock_data_r() - .chain_arc_parking_rwlock_at_kp(DataStruct::name_r()) - .get(&rwlock_write_container, |value| { - println!(" Verified: name = {}", value); - }); - - // Example 8: Write optional field through Arc> - Container::parking_rwlock_data_r() - .then_arc_parking_rwlock_writable_optional_at_kp(DataStruct::optional_value_fw()) - .get_mut(&rwlock_write_container, |value| { - *value = "Modified optional via parking_rwlock".to_string(); - println!("✅ then_arc_parking_rwlock_writable_optional_at_kp (write): Modified optional_value"); - }); - - // Verify the write - Container::parking_rwlock_data_r() - .then_arc_parking_rwlock_optional_at_kp(DataStruct::optional_value_fr()) - .get(&rwlock_write_container, |value| { - println!(" Verified: optional_value = {}", value); - }); - - // ========== PART 2: OptionalKeyPath chains (Option> fields) ========== - println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - println!("PART 2: OptionalKeyPath chains (Option> fields)"); - println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); - - println!("=== OptionalKeyPath -> Arc> Chains ===\n"); - - // Example 9: Read through Option>> - Container::optional_mutex_fr() - .chain_arc_parking_mutex_at_kp(DataStruct::name_r()) - .get(&container, |value| { - println!( - "✅ optional -> chain_arc_parking_mutex_at_kp (read): name = {}", - value - ); - }); - - // Example 10: Read optional field through Option>> - Container::optional_mutex_fr() - .then_arc_parking_mutex_optional_at_kp(DataStruct::optional_value_fr()) - .get(&container, |value| { - println!("✅ optional -> then_arc_parking_mutex_optional_at_kp (read): optional_value = {}", value); - }); - - // Example 11: Write through Option>> - let opt_write_container = Container::new(); - Container::optional_mutex_fr() - .chain_arc_parking_mutex_writable_at_kp(DataStruct::name_w()) - .get_mut(&opt_write_container, |value| { - *value = "Modified via optional parking_mutex chain".to_string(); - println!( - "✅ optional -> chain_arc_parking_mutex_writable_at_kp (write): Modified name" - ); - }); - - // Verify the write - Container::optional_mutex_fr() - .chain_arc_parking_mutex_at_kp(DataStruct::name_r()) - .get(&opt_write_container, |value| { - println!(" Verified: name = {}", value); - }); - - // Example 12: Write optional field through Option>> - Container::optional_mutex_fr() - .then_arc_parking_mutex_writable_optional_at_kp(DataStruct::optional_value_fw()) - .get_mut(&opt_write_container, |value| { - *value = "Modified optional via optional parking_mutex".to_string(); - println!("✅ optional -> then_arc_parking_mutex_writable_optional_at_kp (write): Modified optional_value"); - }); - - // Verify the write - Container::optional_mutex_fr() - .then_arc_parking_mutex_optional_at_kp(DataStruct::optional_value_fr()) - .get(&opt_write_container, |value| { - println!(" Verified: optional_value = {}", value); - }); - - println!("\n=== OptionalKeyPath -> Arc> Chains ===\n"); - - // Example 13: Read through Option>> - Container::optional_rwlock_fr() - .chain_arc_parking_rwlock_at_kp(DataStruct::name_r()) - .get(&container, |value| { - println!( - "✅ optional -> chain_arc_parking_rwlock_at_kp (read): name = {}", - value - ); - }); - - // Example 14: Read optional field through Option>> - Container::optional_rwlock_fr() - .then_arc_parking_rwlock_optional_at_kp(DataStruct::optional_value_fr()) - .get(&container, |value| { - println!("✅ optional -> then_arc_parking_rwlock_optional_at_kp (read): optional_value = {}", value); - }); - - // Example 15: Write through Option>> - let opt_rwlock_write_container = Container::new(); - Container::optional_rwlock_fr() - .chain_arc_parking_rwlock_writable_at_kp(DataStruct::name_w()) - .get_mut(&opt_rwlock_write_container, |value| { - *value = "Modified via optional parking_rwlock chain".to_string(); - println!( - "✅ optional -> chain_arc_parking_rwlock_writable_at_kp (write): Modified name" - ); - }); - - // Verify the write - Container::optional_rwlock_fr() - .chain_arc_parking_rwlock_at_kp(DataStruct::name_r()) - .get(&opt_rwlock_write_container, |value| { - println!(" Verified: name = {}", value); - }); - - // Example 16: Write optional field through Option>> - Container::optional_rwlock_fr() - .then_arc_parking_rwlock_writable_optional_at_kp(DataStruct::optional_value_fw()) - .get_mut(&opt_rwlock_write_container, |value| { - *value = "Modified optional via optional parking_rwlock".to_string(); - println!("✅ optional -> then_arc_parking_rwlock_writable_optional_at_kp (write): Modified optional_value"); - }); - - // Verify the write - Container::optional_rwlock_fr() - .then_arc_parking_rwlock_optional_at_kp(DataStruct::optional_value_fr()) - .get(&opt_rwlock_write_container, |value| { - println!(" Verified: optional_value = {}", value); - }); - - // ========== PART 3: Enum -> Arc> chains ========== - println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - println!("PART 3: Enum case -> Arc> chains"); - println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); - - // Example 17: Read through enum case -> Arc> - Container::state_fr() - .then(AppState::active_r()) - .chain_arc_parking_rwlock_at_kp(Session::user_name_r()) - .get(&container, |value| { - println!( - "✅ enum -> chain_arc_parking_rwlock_at_kp (read): user_name = {}", - value - ); - }); - - // Example 18: Write through enum case -> Arc> - let enum_container = Container::new(); - Container::state_fr() - .then(AppState::active_r()) - .chain_arc_parking_rwlock_writable_at_kp(Session::user_name_w()) - .get_mut(&enum_container, |value| { - *value = "Bob (Updated via enum chain)".to_string(); - println!( - "✅ enum -> chain_arc_parking_rwlock_writable_at_kp (write): Modified user_name" - ); - }); - - // Verify the write - Container::state_fr() - .then(AppState::active_r()) - .chain_arc_parking_rwlock_at_kp(Session::user_name_r()) - .get(&enum_container, |value| { - println!(" Verified: user_name = {}", value); - }); - - // Example 19: Non-matching enum variant returns None - let idle_container = Container::new_idle(); - let result = Container::state_fr() - .then(AppState::active_r()) - .chain_arc_parking_rwlock_at_kp(Session::user_name_r()) - .get(&idle_container, |_| ()); - - if result.is_none() { - println!("✅ enum (Idle) -> None: Correctly returned None for non-matching variant"); - } - - // ========== Comparison: parking_lot vs std::sync ========== - println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - println!("Key Differences: parking_lot vs std::sync"); - println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); - println!("1. parking_lot locks NEVER fail - no Option return for lock operations"); - println!("2. parking_lot is faster - no poisoning overhead"); - println!("3. Same functional keypath chain pattern works for both!"); - println!("4. Both KeyPath AND OptionalKeyPath have full chain support"); - - println!("\n=== All parking_lot chain examples completed successfully! ==="); - } -} - -#[cfg(feature = "parking_lot")] -fn main() { - parking_lot_example::run(); -} - -#[cfg(not(feature = "parking_lot"))] -fn main() { - // This will never run due to compile_error! above - eprintln!("This example requires the 'parking_lot' feature."); - eprintln!("Run with: cargo run --example parking_lot_chains --features parking_lot"); -} diff --git a/examples/parking_lot_nested_chain.rs b/examples/parking_lot_nested_chain.rs deleted file mode 100644 index bbc1d47..0000000 --- a/examples/parking_lot_nested_chain.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Example demonstrating deeply nested parking_lot RwLock chains -//! -//! Run with: cargo run --example parking_lot_nested_chain --features parking_lot -//! -//! This example shows how to: -//! 1. Use derive-generated keypath methods for lock fields -//! 2. Use the new `_parking_fr_at()` and `_parking_fw_at()` helper methods -//! 3. Chain through multiple `Arc>` layers -//! 4. Read and write through nested locks without cloning - -#[cfg(not(feature = "parking_lot"))] -compile_error!( - "This example requires the 'parking_lot' feature. Run with: cargo run --example parking_lot_nested_chain --features parking_lot" -); - -#[cfg(feature = "parking_lot")] -mod example { - use keypaths_proc::Kp; - use parking_lot::RwLock; - use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - use std::sync::Arc; - - #[derive(Kp)] - #[All] // Generate both readable and writable keypaths - pub struct SomeStruct { - pub f1: Arc>, - } - - impl Clone for SomeStruct { - fn clone(&self) -> Self { - panic!("SomeStruct should not be cloned!") - } - } - - impl Clone for SomeOtherStruct { - fn clone(&self) -> Self { - panic!("SomeOtherStruct should not be cloned!") - } - } - - #[derive(Kp)] - #[All] // Generate both readable and writable keypaths - pub struct SomeOtherStruct { - pub f3: Option, - pub f4: Arc>, - } - - #[derive(Kp)] - #[All] // Generate both readable and writable keypaths - pub struct DeeplyNestedStruct { - pub f1: Option, - pub f2: Option, - pub name: String, // Non-optional field - } - - impl Clone for DeeplyNestedStruct { - fn clone(&self) -> Self { - panic!("DeeplyNestedStruct should not be cloned!") - } - } - - pub fn run() { - println!("=== Deeply Nested parking_lot RwLock Chain Example ===\n"); - - let instance = SomeStruct { - f1: Arc::new(RwLock::new(SomeOtherStruct { - f3: Some(String::from("middle_value")), - f4: Arc::new(RwLock::new(DeeplyNestedStruct { - f1: Some(String::from("deep_string")), - f2: Some(42), - name: String::from("initial_name"), - })), - })), - }; - - println!("Initial state:"); - println!(" f3 = {:?}", instance.f1.read().f3); - println!(" f4.f1 = {:?}", instance.f1.read().f4.read().f1); - println!(" f4.f2 = {:?}", instance.f1.read().f4.read().f2); - println!(" f4.name = {:?}", instance.f1.read().f4.read().name); - - println!("\n=== Generated Methods for Arc> Fields ==="); - println!("⚠️ IMPORTANT: RwLock and Mutex DEFAULT to parking_lot!"); - println!(" Use `std::sync::RwLock` or `std::sync::Mutex` for std::sync types.\n"); - println!("For Arc> fields (parking_lot default), the macro generates:"); - println!(" • _r() -> KeyPath>> (readable)"); - println!(" • _w() -> WritableKeyPath>> (writable)"); - println!(" • _fr_at() -> Chain through lock for reading (parking_lot)"); - println!(" • _fw_at() -> Chain through lock for writing (parking_lot)"); - println!("\nFor Arc> fields (explicit prefix), generates:"); - println!(" • _fr_at() -> Chain through lock for reading (std::sync)"); - println!(" • _fw_at() -> Chain through lock for writing (std::sync)"); - - // ============================================================ - // USING THE _fr_at() HELPER METHOD (parking_lot by default) - // ============================================================ - println!("\n--- Using _fr_at() for reading (parking_lot) ---"); - - // Create a keypath to the name field (non-optional) - let name_kp = KeyPath::new(|s: &DeeplyNestedStruct| &s.name); - - // Use the generated f1_fr_at() to chain through the first lock - // (defaults to parking_lot since we used `RwLock` without `std::sync::` prefix) - SomeStruct::f1_fr_at(SomeOtherStruct::f4_r()).get(&instance, |f4_arc| { - // Now chain through the second lock to get the name - SomeOtherStruct::f4_fr_at(name_kp.clone()).get(&instance.f1.read(), |name| { - println!("✅ Read name via _fr_at chain (parking_lot): {:?}", name); - }); - }); - - // Alternative: Direct chaining through both locks - // Using the identity keypath pattern for nested access - let identity_kp = KeyPath::new(|s: &Arc>| s); - - SomeStruct::f1_r() - .chain_arc_parking_rwlock_at_kp(SomeOtherStruct::f4_r()) - .get(&instance, |f4_arc| { - identity_kp - .clone() - .chain_arc_parking_rwlock_at_kp(name_kp.clone()) - .get(f4_arc, |name| { - println!("✅ Read name via nested chain: {:?}", name); - }); - }); - - // ============================================================ - // READING OPTIONAL FIELDS - // ============================================================ - println!("\n--- Reading optional fields through locks ---"); - - // Create keypaths for optional inner fields - let f3_read_kp = OptionalKeyPath::new(|s: &SomeOtherStruct| s.f3.as_ref()); - - // Chain through first lock to read f3 (Option) - SomeStruct::f1_r() - .then_arc_parking_rwlock_optional_at_kp(f3_read_kp.clone()) - .get(&instance, |value| { - println!("✅ Read f3 via chain: {:?}", value); - }); - - // Read through two lock layers to get optional fields - SomeStruct::f1_r() - .chain_arc_parking_rwlock_at_kp(SomeOtherStruct::f4_r()) - .get(&instance, |f4_arc| { - let deep_f1_kp = OptionalKeyPath::new(|s: &DeeplyNestedStruct| s.f1.as_ref()); - let deep_f2_kp = OptionalKeyPath::new(|s: &DeeplyNestedStruct| s.f2.as_ref()); - - identity_kp - .clone() - .then_arc_parking_rwlock_optional_at_kp(deep_f1_kp) - .get(f4_arc, |value| { - println!("✅ Read f4.f1 via nested chain: {:?}", value); - }); - - identity_kp - .clone() - .then_arc_parking_rwlock_optional_at_kp(deep_f2_kp) - .get(f4_arc, |value| { - println!("✅ Read f4.f2 via nested chain: {:?}", value); - }); - }); - - // ============================================================ - // USING THE NEW _parking_fw_at() HELPER METHOD FOR WRITING - // ============================================================ - println!("\n--- Using _parking_fw_at() for writing ---"); - - // Create a writable keypath to the name field (non-optional) - let name_w_kp = WritableKeyPath::new(|s: &mut DeeplyNestedStruct| &mut s.name); - - // Use the generated method to write through both locks - SomeStruct::f1_r() - .chain_arc_parking_rwlock_at_kp(SomeOtherStruct::f4_r()) - .get(&instance, |f4_arc| { - let identity_kp = KeyPath::new(|s: &Arc>| s); - identity_kp - .chain_arc_parking_rwlock_writable_at_kp(name_w_kp.clone()) - .get_mut(f4_arc, |name| { - *name = String::from("updated_name"); - println!("✅ Wrote name via _parking_fw_at chain"); - }); - }); - - // ============================================================ - // WRITING OPTIONAL FIELDS - // ============================================================ - println!("\n--- Writing optional fields through locks ---"); - - // Write to f3 through first lock - let f3_write_kp = WritableOptionalKeyPath::new(|s: &mut SomeOtherStruct| s.f3.as_mut()); - SomeStruct::f1_r() - .then_arc_parking_rwlock_writable_optional_at_kp(f3_write_kp) - .get_mut(&instance, |value| { - *value = String::from("updated_middle_value"); - println!("✅ Wrote to f3 via chain"); - }); - - // Write to deeply nested values through both lock layers - SomeStruct::f1_r() - .chain_arc_parking_rwlock_at_kp(SomeOtherStruct::f4_r()) - .get(&instance, |f4_arc| { - let identity_kp = KeyPath::new(|s: &Arc>| s); - - let deep_f1_w_kp = - WritableOptionalKeyPath::new(|s: &mut DeeplyNestedStruct| s.f1.as_mut()); - let deep_f2_w_kp = - WritableOptionalKeyPath::new(|s: &mut DeeplyNestedStruct| s.f2.as_mut()); - - identity_kp - .clone() - .then_arc_parking_rwlock_writable_optional_at_kp(deep_f1_w_kp) - .get_mut(f4_arc, |value| { - *value = String::from("updated_deep_string"); - println!("✅ Wrote to f4.f1 via nested chain"); - }); - - identity_kp - .then_arc_parking_rwlock_writable_optional_at_kp(deep_f2_w_kp) - .get_mut(f4_arc, |value| { - *value = 100; - println!("✅ Wrote to f4.f2 via nested chain"); - }); - }); - - // ============================================================ - // VERIFY THE WRITES - // ============================================================ - println!("\n--- Verifying writes ---"); - println!(" f3 = {:?}", instance.f1.read().f3); - println!(" f4.f1 = {:?}", instance.f1.read().f4.read().f1); - println!(" f4.f2 = {:?}", instance.f1.read().f4.read().f2); - println!(" f4.name = {:?}", instance.f1.read().f4.read().name); - - println!("\n=== Key Takeaways ==="); - println!("✅ No cloning occurred - all access was zero-copy!"); - println!("✅ Used derive-generated keypaths: SomeStruct::f1_r(), SomeOtherStruct::f4_r()"); - println!("✅ Used _parking_fr_at() and _parking_fw_at() for simplified chaining"); - println!("✅ Chained through multiple Arc> layers safely"); - println!("✅ Works with parking_lot for better performance (no lock poisoning)"); - } -} - -#[cfg(feature = "parking_lot")] -fn main() { - example::run(); -} - -#[cfg(not(feature = "parking_lot"))] -fn main() { - eprintln!("This example requires the 'parking_lot' feature."); - eprintln!("Run with: cargo run --example parking_lot_nested_chain --features parking_lot"); -} diff --git a/examples/parking_lot_simple.rs b/examples/parking_lot_simple.rs deleted file mode 100644 index 06e1158..0000000 --- a/examples/parking_lot_simple.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Simple example demonstrating parking_lot RwLock with keypaths -//! -//! Run with: cargo run --example parking_lot_simple --features parking_lot -//! -//! This example shows how to: -//! 1. Use derive-generated keypath methods for lock fields -//! 2. Read and write through a single Arc> -//! 3. Chain keypaths through the lock to access inner fields - -#[cfg(not(feature = "parking_lot"))] -compile_error!( - "This example requires the 'parking_lot' feature. Run with: cargo run --example parking_lot_simple --features parking_lot" -); - -#[cfg(feature = "parking_lot")] -mod example { - use keypaths_proc::Kp; - use parking_lot::RwLock; - use std::sync::Arc; - - #[derive(Kp)] - #[All] // Generate all methods (readable, writable, owned) - pub struct AppState { - pub user_data: Arc>, - } - - impl Clone for AppState { - fn clone(&self) -> Self { - panic!("AppState should not be cloned!") - } - } - - #[derive(Kp)] - #[All] // Generate all methods (readable, writable, owned) - pub struct UserData { - pub name: String, - pub age: u32, - pub email: Option, - } - - impl Clone for UserData { - fn clone(&self) -> Self { - panic!("UserData should not be cloned!") - } - } - - pub fn run() { - println!("=== Simple parking_lot RwLock Example ===\n"); - - let state = AppState { - user_data: Arc::new(RwLock::new(UserData { - name: String::from("Akash"), - age: 30, - email: Some(String::from("alice@example.com")), - })), - }; - - println!("Initial state:"); - println!(" name = {:?}", state.user_data.read().name); - println!(" age = {:?}", state.user_data.read().age); - println!(" email = {:?}", state.user_data.read().email); - - println!("\n=== Using Generated Keypath Methods ==="); - println!("For Arc> fields, the macro generates:"); - println!(" • _r() -> KeyPath>> (readable)"); - println!(" • _w() -> WritableKeyPath>> (writable)"); - println!(" • _fr_at() -> Chain through lock for reading (parking_lot)"); - println!(" • _fw_at() -> Chain through lock for writing (parking_lot)\n"); - - // ============================================================ - // READING THROUGH THE LOCK - // ============================================================ - println!("--- Reading through lock ---"); - - // Read name field through the lock - AppState::user_data_fr_at(UserData::name_r()).get(&state, |name| { - println!("✅ Read name via keypath: {:?}", name); - }); - - // Read age field through the lock - AppState::user_data_fr_at(UserData::age_r()).get(&state, |age| { - println!("✅ Read age via keypath: {:?}", age); - }); - - // Read optional email field through the lock - AppState::user_data_fr_at(UserData::email_r()).get(&state, |email| { - println!("✅ Read email via keypath: {:?}", email); - }); - - // ============================================================ - // WRITING THROUGH THE LOCK - // ============================================================ - println!("\n--- Writing through lock ---"); - - // Write to name field - AppState::user_data_fw_at(UserData::name_w()).get_mut(&state, |name| { - *name = String::from("Bob"); - println!("✅ Updated name to: {:?}", name); - }); - - // Write to age field - AppState::user_data_fw_at(UserData::age_w()).get_mut(&state, |age| { - *age = 35; - println!("✅ Updated age to: {:?}", age); - }); - - // Write to optional email field - AppState::user_data_fw_at(UserData::email_w()).get_mut(&state, |email| { - *email = Some(String::from("bob@example.com")); - println!("✅ Updated email to: {:?}", email); - }); - - // ============================================================ - // VERIFY THE WRITES - // ============================================================ - println!("\n--- Final state ---"); - println!(" name = {:?}", state.user_data.read().name); - println!(" age = {:?}", state.user_data.read().age); - println!(" email = {:?}", state.user_data.read().email); - - println!("\n=== Key Takeaways ==="); - println!("✅ No cloning occurred - all access was zero-copy!"); - println!( - "✅ Used derive-generated keypaths: AppState::user_data_fr_at(), AppState::user_data_fw_at()" - ); - println!("✅ Chained through Arc> safely"); - println!("✅ Works with parking_lot for better performance (no lock poisoning)"); - } -} - -#[cfg(feature = "parking_lot")] -fn main() { - example::run(); -} - -#[cfg(not(feature = "parking_lot"))] -fn main() { - eprintln!("This example requires the 'parking_lot' feature."); - eprintln!("Run with: cargo run --example parking_lot_simple --features parking_lot"); -} diff --git a/examples/partial_any_aggregator_example.rs b/examples/partial_any_aggregator_example.rs deleted file mode 100644 index 5579ba9..0000000 --- a/examples/partial_any_aggregator_example.rs +++ /dev/null @@ -1,297 +0,0 @@ -use keypaths_proc::{AnyKeypaths, Kp, PartialKp}; -use rust_keypaths::{AnyKeyPath, PartialKeyPath}; -use std::collections::HashMap; -use std::rc::Rc; -use std::sync::{Arc, Mutex, RwLock}; - -#[derive(Debug, Clone, Kp, PartialKp, AnyKeypaths)] -struct Person { - name: String, - age: u32, - email: Option, - metadata: HashMap, -} - -#[derive(Debug, Clone, Kp, PartialKp, AnyKeypaths)] -struct Company { - name: String, - employees: Vec, - revenue: f64, -} - -fn main() { - println!("=== PartialKeyPath and AnyKeyPath Aggregator Functions Example ===\n"); - - // Create sample data - let person = Person { - name: "Akash".to_string(), - age: 30, - email: Some("akash@example.com".to_string()), - metadata: [("department".to_string(), "engineering".to_string())].into(), - }; - - let company = Company { - name: "TechCorp".to_string(), - employees: vec![person.clone()], - revenue: 1000000.0, - }; - - // ===== PartialKeyPath Aggregator Examples ===== - println!("--- 1. PartialKeyPath Aggregator Functions ---"); - - // Create a partial keypath for Person::name - let name_partial = Person::name_partial_r(); - - // Test Arc aggregator - let person_arc = Arc::new(person.clone()); - let name_arc_partial = name_partial.clone().for_arc(); - if let Some(name) = name_arc_partial.get_as::(&person_arc) { - println!("Person name via Arc (partial): {:?}", name); - } - - // Test Box aggregator - let person_box = Box::new(person.clone()); - let name_box_partial = name_partial.clone().for_box(); - if let Some(name) = name_box_partial.get_as::(&person_box) { - println!("Person name via Box (partial): {:?}", name); - } - - // Test Rc aggregator - let person_rc = Rc::new(person.clone()); - let name_rc_partial = name_partial.clone().for_rc(); - if let Some(name) = name_rc_partial.get_as::(&person_rc) { - println!("Person name via Rc (partial): {:?}", name); - } - - // Test Option aggregator - let person_option = Some(person.clone()); - let name_option_partial = name_partial.clone().for_option(); - if let Some(Some(name)) = name_option_partial.get_as::(&person_option) { - println!("Person name via Option (partial): {:?}", name); - } - - // Test Result aggregator - let person_result: Result = Ok(person.clone()); - let name_result_partial = name_partial.clone().for_result::(); - if let Some(Some(name)) = name_result_partial.get_as::(&person_result) { - println!( - "Person name via Result (partial): {:?}", - name - ); - } - - // Test Arc aggregator - need to clone the root first - let person_arc_rwlock = Arc::new(RwLock::new(person.clone())); - let cloned_person = person_arc_rwlock.read().unwrap().clone(); - if let Some(name) = name_partial.get_as::(&cloned_person) { - println!("Person name via Arc> (partial): {:?}", name); - } - - // Test Arc aggregator - need to clone the root first - let person_arc_mutex = Arc::new(Mutex::new(person.clone())); - let cloned_person = person_arc_mutex.lock().unwrap().clone(); - if let Some(name) = name_partial.get_as::(&cloned_person) { - println!("Person name via Arc> (partial): {:?}", name); - } - - // ===== AnyKeyPath Aggregator Examples ===== - println!("\n--- 2. AnyKeyPath Aggregator Functions ---"); - - // Create an any keypath for Person::name - let name_any = Person::name_any_r(); - - // Test Arc aggregator - let person_arc_boxed: Box = Box::new(Arc::new(person.clone())); - let name_arc_any = name_any.clone().for_arc::(); - if let Some(value) = name_arc_any.get(&*person_arc_boxed) { - println!("Person name via Arc (any): {:?}", value); - } - - // Test Box aggregator - let person_box_boxed: Box = Box::new(Box::new(person.clone())); - let name_box_any = name_any.clone().for_box::(); - if let Some(value) = name_box_any.get(&*person_box_boxed) { - println!("Person name via Box (any): {:?}", value); - } - - // Test Rc aggregator (using Arc since Rc is not Send + Sync) - let person_arc_boxed2: Box = - Box::new(Arc::new(person.clone())); - let name_arc_any2 = name_any.clone().for_arc::(); - if let Some(value) = name_arc_any2.get(&*person_arc_boxed2) { - println!("Person name via Arc #2 (any): {:?}", value); - } - - // Test Option aggregator - let person_option_boxed: Box = Box::new(Some(person.clone())); - let name_option_any = name_any.clone().for_option::(); - if let Some(value) = name_option_any.get(&*person_option_boxed) { - println!("Person name via Option (any): {:?}", value); - } - - // Test Result aggregator - let person_result_boxed: Box = - Box::new(Ok::(person.clone())); - let name_result_any = name_any.clone().for_result::(); - if let Some(value) = name_result_any.get(&*person_result_boxed) { - println!("Person name via Result (any): {:?}", value); - } - - // Test Arc aggregator - need to clone the root first - let person_arc_rwlock_boxed: Box = - Box::new(Arc::new(RwLock::new(person.clone()))); - if let Some(arc_rwlock) = person_arc_rwlock_boxed.downcast_ref::>>() { - let cloned_person = arc_rwlock.read().unwrap().clone(); - if let Some(name) = name_any.get_as::(&cloned_person) { - if let Some(name) = name { - println!("Person name via Arc> (any): {:?}", name); - } - } - } - - // Test Arc aggregator - need to clone the root first - let person_arc_mutex_boxed: Box = - Box::new(Arc::new(Mutex::new(person.clone()))); - if let Some(arc_mutex) = person_arc_mutex_boxed.downcast_ref::>>() { - let cloned_person = arc_mutex.lock().unwrap().clone(); - if let Some(name) = name_any.get_as::(&cloned_person) { - if let Some(name) = name { - println!("Person name via Arc> (any): {:?}", name); - } - } - } - - // ===== Mixed Container Types ===== - println!("\n--- 3. Mixed Container Types ---"); - - // Create a collection of different container types - let containers: Vec> = vec![ - Box::new(person.clone()), - Box::new(Arc::new(person.clone())), - Box::new(Box::new(person.clone())), - Box::new(Arc::new(person.clone())), - Box::new(Some(person.clone())), - Box::new(Ok::(person.clone())), - Box::new(Arc::new(RwLock::new(person.clone()))), - Box::new(Arc::new(Mutex::new(person.clone()))), - ]; - - // Create different keypaths - let name_partial = Person::name_partial_r(); - let age_partial = Person::age_partial_r(); - let email_partial = Person::email_partial_fr(); - - // Test with different aggregators - for (i, container) in containers.iter().enumerate() { - match i { - 0 => { - // Direct Person - if let Some(person_ref) = container.downcast_ref::() { - if let Some(name) = name_partial.get_as::(person_ref) { - println!("Container {} (Person): {:?}", i, name); - } - } - } - 1 => { - // Arc - if let Some(arc_ref) = container.downcast_ref::>() { - let name_arc_partial = name_partial.clone().for_arc(); - if let Some(name) = name_arc_partial.get_as::(arc_ref) { - println!("Container {} (Arc): {:?}", i, name); - } - } - } - 2 => { - // Box - if let Some(box_ref) = container.downcast_ref::>() { - let name_box_partial = name_partial.clone().for_box(); - if let Some(name) = name_box_partial.get_as::(box_ref) { - println!("Container {} (Box): {:?}", i, name); - } - } - } - 3 => { - // Arc #2 - if let Some(arc_ref) = container.downcast_ref::>() { - let name_arc_partial = name_partial.clone().for_arc(); - if let Some(name) = name_arc_partial.get_as::(arc_ref) { - println!("Container {} (Arc #2): {:?}", i, name); - } - } - } - 4 => { - // Option - if let Some(option_ref) = container.downcast_ref::>() { - let name_option_partial = name_partial.clone().for_option(); - if let Some(Some(name)) = name_option_partial.get_as::(option_ref) { - println!("Container {} (Option): {:?}", i, name); - } - } - } - 5 => { - // Result - if let Some(result_ref) = container.downcast_ref::>() { - let name_result_partial = name_partial.clone().for_result::(); - if let Some(Some(name)) = name_result_partial.get_as::(result_ref) { - println!("Container {} (Result): {:?}", i, name); - } - } - } - 6 => { - // Arc> - need to clone the root first - if let Some(arc_rwlock_ref) = container.downcast_ref::>>() { - let cloned_person = arc_rwlock_ref.read().unwrap().clone(); - if let Some(name) = name_partial.get_as::(&cloned_person) { - println!("Container {} (Arc>): {:?}", i, name); - } - } - } - 7 => { - // Arc> - need to clone the root first - if let Some(arc_mutex_ref) = container.downcast_ref::>>() { - let cloned_person = arc_mutex_ref.lock().unwrap().clone(); - if let Some(name) = name_partial.get_as::(&cloned_person) { - println!("Container {} (Arc>): {:?}", i, name); - } - } - } - _ => {} - } - } - - // ===== Composition with Aggregators ===== - println!("\n--- 4. Composition with Aggregators ---"); - - // Create a company with Arc> employees - let employee = Arc::new(RwLock::new(person.clone())); - let company_with_arc_employees = Company { - name: "TechCorp".to_string(), - employees: vec![employee.read().unwrap().clone()], - revenue: 1000000.0, - }; - - // Create keypaths for company name and first employee name - let company_name_partial = Company::name_partial_r(); - let employee_name_partial = Person::name_partial_r(); - - // Access company name directly - if let Some(name) = company_name_partial.get_as::(&company_with_arc_employees) { - println!("Company name: {:?}", name); - } - - // Access first employee name through composition - let first_employee_partial = Company::employees_partial_fr_at(0); - if let Some(value) = first_employee_partial.get(&company_with_arc_employees) { - println!("First employee (type-erased): {:?}", value); - } - - println!("\n✅ PartialKeyPath and AnyKeyPath Aggregator Functions Example completed!"); - println!("📝 This example demonstrates:"); - println!(" • PartialKeyPath aggregator functions (for_arc, for_box, for_rc, etc.)"); - println!(" • AnyKeyPath aggregator functions with type parameters"); - println!(" • Working with different container types (Arc, Box, Rc, Option, Result, etc.)"); - println!(" • Thread-safe containers (Arc>, Arc>)"); - println!(" • Mixed container types in collections"); - println!(" • Composition with aggregator functions"); - println!(" • Full integration with derive macros!"); -} diff --git a/examples/prism.rs b/examples/prism.rs deleted file mode 100644 index 4bb27fb..0000000 --- a/examples/prism.rs +++ /dev/null @@ -1,38 +0,0 @@ -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -#[derive(Debug)] -enum Payment { - Cash { amount: u32 }, - Card { number: String, cvv: String }, -} - -fn main() { - // let kp = KeyPaths::Prism { - // extract: Rc::new(|p: &Payment| match p { - // Payment::Cash { amount } => Some(amount), - // _ => None, - // }), - // // embed: Rc::new(|v| Payment::Cash { amount: v }), - // embed: Rc::new(|v| Payment::Cash { amount: v.clone() }), - // }; - let kp = WritableOptionalKeyPath::writable_enum( - |v| Payment::Cash { amount: v }, - |p: &Payment| match p { - Payment::Cash { amount } => Some(amount), - _ => None, - }, - |p: &mut Payment| match p { - Payment::Cash { amount } => Some(amount), - _ => None, - }, - ); - - let mut p = Payment::Cash { amount: 10 }; - - println!("{:?}", p); - - if let Some(v) = kp.get_mut(&mut p) { - *v = 34; - } - - println!("{:?}", p); -} diff --git a/examples/prism_compose.rs b/examples/prism_compose.rs deleted file mode 100644 index 0716165..0000000 --- a/examples/prism_compose.rs +++ /dev/null @@ -1,72 +0,0 @@ -use rust_keypaths::WritableOptionalKeyPath; - -#[derive(Debug)] -struct Size { - width: u32, - height: u32, -} - -#[derive(Debug)] -enum Color { - Red, - Green, - Blue, - Other(RGBU8), -} - -#[derive(Debug)] -struct RGBU8(u8, u8, u8); - -#[derive(Debug)] -struct ABox { - name: String, - size: Size, - color: Color, -} - -#[derive(Debug)] -struct Rectangle { - size: Size, - name: String, -} - -fn main() { - let mut a_box = ABox { - name: String::from("A box"), - size: Size { - width: 10, - height: 20, - }, - color: Color::Other(RGBU8(10, 20, 30)), - }; - - // Create a writable keypath for the color field - let color_kp = WritableOptionalKeyPath::new(|x: &mut ABox| Some(&mut x.color)); - - // Create a writable enum keypath for the Other variant - let case_path = WritableOptionalKeyPath::writable_enum( - |v| Color::Other(v), - |p: &Color| match p { - Color::Other(rgb) => Some(rgb), - _ => None, - }, - |p: &mut Color| match p { - Color::Other(rgb) => Some(rgb), - _ => None, - }, - ); - - // Compose color with rgb - println!("{:?}", a_box); - let color_rgb_kp = color_kp.then(case_path); - if let Some(value) = color_rgb_kp.get_mut(&mut a_box) { - *value = RGBU8(0, 0, 0); - } - - println!("{:?}", a_box); -} - -/* -ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(10, 20, 30)) } -ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0, 0, 0)) } -*/ diff --git a/examples/prism_compose2.rs b/examples/prism_compose2.rs deleted file mode 100644 index 0088c57..0000000 --- a/examples/prism_compose2.rs +++ /dev/null @@ -1,118 +0,0 @@ -use rust_keypaths::WritableOptionalKeyPath; - -// Example usage (SOUND: User actually owns Address) -#[derive(Debug)] -struct Address { - city: String, - zip: String, -} - -#[derive(Debug)] -struct User { - name: String, - age: u32, - address: Address, -} - -#[derive(Debug)] -pub enum Product { - Book(Book), - Electronics(Electronics), - Apparel, -} - -#[derive(Debug)] -pub struct Book { - title: String, - price: f64, -} - -#[derive(Debug)] -pub struct Electronics { - name: String, - price: f64, - warranty: u32, -} - -#[derive(Debug)] -pub struct Inventory { - items: Vec, - shipping_cost: f64, -} - -// Add a helper method to the Product enum for easy display of price -impl Product { - fn price(&self) -> Option<&f64> { - match self { - Product::Book(b) => Some(&b.price), - Product::Electronics(e) => Some(&e.price), - _ => None, - } - } -} - -fn main() { - // invalid syntx as there is nothing in Apparel to read or write. - // let kp:KeyPaths = KeyPaths::writable_enum( - // |v| Product::Apparel, - // |p: &Product| match p { - // Product::Apparel => Some(&()), - // _ => None, - // }, - // |p: &mut Product| match p { - // Product::Apparel => Some(&mut ()), - // _ => None, - // }, - // ); - - let mut inventory = Inventory { - items: vec![ - Product::Book(Book { - title: "The Rust Programming Language".to_string(), - price: 50.0, - }), - Product::Electronics(Electronics { - name: "Smartphone".to_string(), - price: 699.99, - warranty: 1, - }), - Product::Apparel, - ], - shipping_cost: 5.0, - }; - - // Create writable enum keypath for Electronics variant - let electronics_path = WritableOptionalKeyPath::writable_enum( - |v| Product::Electronics(v), - |p: &Product| match p { - Product::Electronics(electronics) => Some(electronics), - _ => None, - }, - |p: &mut Product| match p { - Product::Electronics(electronics) => Some(electronics), - _ => None, - }, - ); - - let price_path = WritableOptionalKeyPath::new(|e: &mut Electronics| Some(&mut e.price)); - - // Product -> Electronics -> price - let product_to_price = electronics_path.then(price_path); - - // Apply the composed KeyPath - if let Some(price) = product_to_price.get_mut(&mut inventory.items[1]) { - println!("Original smartphone price: ${}", price); - *price = 649.99; - println!("New smartphone price: ${:?}", inventory.items[1].price()); - } else { - println!("Path not found for this product."); - } - - // Product -> Book -> price - // Now, try on a product that doesn't match the path - if let Some(_) = product_to_price.get_mut(&mut inventory.items[0]) { - // This won't be executed - } else { - println!("Path not found for the book."); - } -} diff --git a/examples/prism_compose_macros.rs b/examples/prism_compose_macros.rs deleted file mode 100644 index 475ece8..0000000 --- a/examples/prism_compose_macros.rs +++ /dev/null @@ -1,60 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::WritableOptionalKeyPath; - -#[derive(Debug, Kp)] -#[All] -struct Size { - width: u32, - height: u32, -} - -#[derive(Debug)] -enum Color { - Red, - Green, - Blue, - Other(RGBU8), -} - -#[derive(Debug)] -struct RGBU8(u8, u8, u8); - -#[derive(Debug, Kp)] -#[All] -struct ABox { - name: String, - size: Size, - color: Color, -} - -fn main() { - let mut a_box = ABox { - name: String::from("A box"), - size: Size { - width: 10, - height: 20, - }, - color: Color::Other(RGBU8(10, 20, 30)), - }; - - // Get writable keypath for color field and convert to optional for chaining - let color_kp = ABox::color_w().to_optional(); - let case_path = WritableOptionalKeyPath::writable_enum( - |v| Color::Other(v), - |c: &Color| match c { - Color::Other(rgb) => Some(rgb), - _ => None, - }, - |c: &mut Color| match c { - Color::Other(rgb) => Some(rgb), - _ => None, - }, - ); - - let color_rgb_kp = color_kp.then(case_path); - if let Some(value) = color_rgb_kp.get_mut(&mut a_box) { - *value = RGBU8(0, 0, 0); - } - - println!("{:?}", a_box); -} diff --git a/examples/proc_macro_expended.rs b/examples/proc_macro_expended.rs deleted file mode 100644 index e7ca892..0000000 --- a/examples/proc_macro_expended.rs +++ /dev/null @@ -1,96 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug)] -struct SomeComplexStruct { - scsf: Option, - // scsf2: Option, -} - -impl SomeComplexStruct { - // read only keypath = field_name_r - // fn r() -> KeyPaths{ - // KeyPath::new(get) - // } - - // write only keypath = field_name_w - // fn w() -> KeyPaths<>{} - - // failable read only keypath = field_name_fr - fn scsf_fr() -> OptionalKeyPath< - SomeComplexStruct, - SomeOtherStruct, - impl for<'r> Fn(&'r SomeComplexStruct) -> Option<&'r SomeOtherStruct>, - > { - OptionalKeyPath::new(|root: &SomeComplexStruct| root.scsf.as_ref()) - } - - // failable writeable keypath = field_name_fw - fn scsf_fw() -> WritableOptionalKeyPath< - SomeComplexStruct, - SomeOtherStruct, - impl for<'r> Fn(&'r mut SomeComplexStruct) -> Option<&'r mut SomeOtherStruct>, - > { - WritableOptionalKeyPath::new(|root: &mut SomeComplexStruct| root.scsf.as_mut()) - } -} - -impl SomeComplexStruct { - fn new() -> Self { - Self { - scsf: Some(SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - }, - }), - } - } -} - -#[derive(Debug, Kp)] -#[All] -struct SomeOtherStruct { - sosf: OneMoreStruct, -} - -#[derive(Debug, Kp)] -#[All] -struct OneMoreStruct { - omsf: String, -} - -fn main() { - // imparitive way - // let mut instance = SomeComplexStruct::new(); - // if let Some(inner_filed) = &mut instance.scsf { - // let inner_most_field = &mut inner_filed.sosf.omsf; - // *inner_most_field = String::from("we can change the field with the imparitive"); - // } - // println!("instance = {:?}", instance); - - // the other way - // SomeComplexStruct -> SomeOtherStruct -> OneMoreStruct -> omsf - - // let scsfp: KeyPath Fn(&\'r SomeComplexStruct) -> &\'r SomeOtherStruct> = SomeComplexStruct::scsf_fw(); - // let sosfp: key_paths_core::KeyPaths = - // SomeOtherStruct::sosf_fw(); - // let omsfp: key_paths_core::KeyPaths = OneMoreStruct::omsf_fw(); - // let op: KeyPath Fn(&\'r SomeComplexStruct) -> &\'r String> = scsfp.then(sosfp).then(omsfp); - // let mut instance = SomeComplexStruct::new(); - // let omsf = op.get_mut(&mut instance); - // **omsf = - // String::from("we can change the field with the other way unlocked by keypaths"); - // println!("instance = {:?}", instance); - - // syntictic suger to do what we just do with other way - // SomeComplexStruct -> SomeOtherStruct -> OneMoreStruct -> omsf - - let op = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omsf_fw()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get_mut(&mut instance) { - *omsf = String::from("we can change the field with the other way unlocked by keypaths"); - } - println!("instance = {:?}", instance); -} diff --git a/examples/query_builder.rs b/examples/query_builder.rs deleted file mode 100644 index 0cc1d6a..0000000 --- a/examples/query_builder.rs +++ /dev/null @@ -1,363 +0,0 @@ -// Demonstrates using rust-key-paths for building dynamic queries -// This example shows how to: -// 1. Build type-safe query filters using keypaths -// 2. Chain multiple predicates together -// 3. Execute queries on in-memory data -// 4. Access nested fields in query predicates -// cargo run --example query_builder - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Clone, Kp)] -#[All] -struct Product { - name: String, - price: f64, - details: ProductDetails, -} - -#[derive(Debug, Clone, Kp)] -#[All] -struct ProductDetails { - category: String, - in_stock: bool, - rating: f64, -} - -// Query builder using keypaths -// This provides a fluent API for building complex queries over collections -// Key benefits: -// - Type-safe field access through keypaths -// - Composable filters that can be chained -// - Works with nested fields seamlessly -// - Zero runtime overhead after compilation -struct Query { - filters: Vec bool>>, -} - -impl Query { - fn new() -> Self { - Self { - filters: Vec::new(), - } - } - - // Add a filter predicate using a keypath - // The keypath provides type-safe access to the field, - // and the predicate defines the filtering logic - // Note: Use readable keypaths (_r) for queries since we only need read access - fn where_( - mut self, - path: KeyPath, - predicate: impl Fn(&F) -> bool + 'static, - ) -> Self - where - F: 'static, - P: for<'r> Fn(&'r T) -> &'r F + 'static, - { - let path_rc = std::rc::Rc::new(path); - let path_clone = path_rc.clone(); - self.filters - .push(Box::new(move |item| predicate(path_clone.get(item)))); - self - } - - // Add a filter predicate using an optional keypath - // This handles cases where the field might not exist (Option, nested fields, etc.) - fn where_optional( - mut self, - path: OptionalKeyPath, - predicate: impl Fn(&F) -> bool + 'static, - ) -> Self - where - F: 'static, - P: for<'r> Fn(&'r T) -> Option<&'r F> + 'static, - { - let path_rc = std::rc::Rc::new(path); - let path_clone = path_rc.clone(); - self.filters.push(Box::new(move |item| { - path_clone.get(item).map_or(false, |val| predicate(val)) - })); - self - } - - // Execute the query and return matching items - fn execute<'a>(&self, items: &'a [T]) -> Vec<&'a T> { - items - .iter() - .filter(|item| self.filters.iter().all(|f| f(item))) - .collect() - } - - // Execute the query and return mutable references - fn execute_mut<'a>(&self, items: &'a mut [T]) -> Vec<&'a mut T> { - items - .iter_mut() - .filter(|item| self.filters.iter().all(|f| f(&**item))) - .collect() - } - - // Count matching items without allocating - fn count(&self, items: &[T]) -> usize { - items - .iter() - .filter(|item| self.filters.iter().all(|f| f(item))) - .count() - } -} - -// Helper function to create sample products -fn create_sample_products() -> Vec { - vec![ - Product { - name: "Laptop".to_string(), - price: 999.99, - details: ProductDetails { - category: "Electronics".to_string(), - in_stock: true, - rating: 4.5, - }, - }, - Product { - name: "Mouse".to_string(), - price: 29.99, - details: ProductDetails { - category: "Electronics".to_string(), - in_stock: false, - rating: 4.2, - }, - }, - Product { - name: "Keyboard".to_string(), - price: 79.99, - details: ProductDetails { - category: "Electronics".to_string(), - in_stock: true, - rating: 4.7, - }, - }, - Product { - name: "Desk Chair".to_string(), - price: 249.99, - details: ProductDetails { - category: "Furniture".to_string(), - in_stock: true, - rating: 4.3, - }, - }, - Product { - name: "Monitor".to_string(), - price: 349.99, - details: ProductDetails { - category: "Electronics".to_string(), - in_stock: true, - rating: 4.8, - }, - }, - Product { - name: "Desk Lamp".to_string(), - price: 39.99, - details: ProductDetails { - category: "Furniture".to_string(), - in_stock: false, - rating: 3.9, - }, - }, - Product { - name: "USB Cable".to_string(), - price: 12.99, - details: ProductDetails { - category: "Electronics".to_string(), - in_stock: true, - rating: 4.1, - }, - }, - ] -} - -// Usage: Build type-safe queries -fn main() { - println!("=== Query Builder Demo ===\n"); - - let products = create_sample_products(); - - println!("Total products in database: {}\n", products.len()); - - // Query 1: Electronics, in stock, price < 1000, rating > 4.0 - println!("--- Query 1: Premium Electronics in Stock ---"); - let query1 = Query::new() - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Electronics", - ) - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::in_stock_r().to_optional()), - |&in_stock| in_stock, - ) - .where_(Product::price_r(), |&price| price < 1000.0) - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::rating_r().to_optional()), - |&rating| rating > 4.0, - ); - - let results1 = query1.execute(&products); - println!("Found {} products:", results1.len()); - for product in results1 { - println!( - " - {} (${:.2}) - Rating: {:.1} - In Stock: {}", - product.name, product.price, product.details.rating, product.details.in_stock - ); - } - - // Query 2: Budget items under $50 - println!("\n--- Query 2: Budget Items Under $50 ---"); - let query2 = Query::new().where_(Product::price_r(), |&price| price < 50.0); - - let results2 = query2.execute(&products); - println!("Found {} products:", results2.len()); - for product in results2 { - println!( - " - {} (${:.2}) - {}", - product.name, product.price, product.details.category - ); - } - - // Query 3: Out of stock items - println!("\n--- Query 3: Out of Stock Items ---"); - let query3 = Query::new().where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::in_stock_r().to_optional()), - |&in_stock| !in_stock, - ); - - let results3 = query3.execute(&products); - println!("Found {} products:", results3.len()); - for product in results3 { - println!(" - {} (${:.2})", product.name, product.price); - } - - // Query 4: Highly rated furniture (rating >= 4.0) - println!("\n--- Query 4: Highly Rated Furniture ---"); - let query4 = Query::new() - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Furniture", - ) - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::rating_r().to_optional()), - |&rating| rating >= 4.0, - ); - - let results4 = query4.execute(&products); - println!("Found {} products:", results4.len()); - for product in results4 { - println!( - " - {} (${:.2}) - Rating: {:.1}", - product.name, product.price, product.details.rating - ); - } - - // Query 5: Count products by category - println!("\n--- Query 5: Products by Category ---"); - let electronics_query = Query::new().where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Electronics", - ); - - let furniture_query = Query::new().where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Furniture", - ); - - println!( - "Electronics: {} products", - electronics_query.count(&products) - ); - println!("Furniture: {} products", furniture_query.count(&products)); - - // Query 6: Complex query - Mid-range products - println!("\n--- Query 6: Mid-Range Products ($30-$300) with Good Ratings ---"); - let query6 = Query::new() - .where_(Product::price_r(), |&price| price >= 30.0 && price <= 300.0) - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::rating_r().to_optional()), - |&rating| rating >= 4.0, - ) - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::in_stock_r().to_optional()), - |&in_stock| in_stock, - ); - - let results6 = query6.execute(&products); - println!("Found {} products:", results6.len()); - for product in results6 { - println!( - " - {} (${:.2}) - {} - Rating: {:.1}", - product.name, product.price, product.details.category, product.details.rating - ); - } - - // Demonstrate mutable query results - println!("\n--- Applying Discount to Electronics Over $100 ---"); - let mut products_mut = products.clone(); - - let discount_query = Query::new() - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Electronics", - ) - .where_(Product::price_r(), |&price| price > 100.0); - - let to_discount = discount_query.execute_mut(&mut products_mut); - println!("Applying 10% discount to {} products:", to_discount.len()); - - for product in to_discount { - let old_price = product.price; - product.price *= 0.9; // 10% discount - println!( - " - {}: ${:.2} -> ${:.2}", - product.name, old_price, product.price - ); - } - - // Verify the changes - println!("\n--- Verification: Electronics Over $100 (After Discount) ---"); - let verify_query = Query::new() - .where_optional( - Product::details_r() - .to_optional() - .then(ProductDetails::category_r().to_optional()), - |cat| cat == "Electronics", - ) - .where_(Product::price_r(), |&price| price > 100.0); - - let after_discount = verify_query.execute(&products_mut); - println!("Products still over $100: {}", after_discount.len()); - for product in after_discount { - println!(" - {} (${:.2})", product.name, product.price); - } - - println!("\n✓ Query builder demo complete!"); -} diff --git a/examples/rc_keypath.rs b/examples/rc_keypath.rs deleted file mode 100644 index eabdbd8..0000000 --- a/examples/rc_keypath.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{rc::Rc, sync::Arc}; - -use keypaths_proc::{Casepaths, Kp}; - -#[derive(Debug, Kp)] -#[All] -struct SomeComplexStruct { - scsf: Rc, - // scsf2: Option>, -} - -impl SomeComplexStruct { - fn new() -> Self { - Self { - scsf: Rc::new(SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }), - // scsf2: None, - } - } -} - -#[derive(Debug, Kp, Clone)] -#[All] -struct SomeOtherStruct { - sosf: OneMoreStruct, -} - -#[derive(Debug, Casepaths, Clone)] -#[All] -enum SomeEnum { - A(String), - B(DarkStruct), -} - -#[derive(Debug, Kp, Clone)] -#[All] -struct OneMoreStruct { - omsf: String, - omse: SomeEnum, -} - -#[derive(Debug, Kp, Clone)] -#[All] -struct DarkStruct { - dsf: String, -} - -fn main() { - // let x = SomeComplexStruct::scsf2_fw().then(SomeOtherStruct::sosf_fw()); - let op = SomeComplexStruct::scsf_fr() - .then(SomeOtherStruct::sosf_fr()) - .then(OneMoreStruct::omse_fr()) - .then(SomeEnum::b_r()) - .then(DarkStruct::dsf_fr()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get(&instance) { - // *omsf = - // String::from("we can change the field with the other way unlocked by keypaths"); - println!("instance = {:?}", instance); - } -} diff --git a/examples/readable_keypaths.rs b/examples/readable_keypaths.rs deleted file mode 100644 index 4382950..0000000 --- a/examples/readable_keypaths.rs +++ /dev/null @@ -1,26 +0,0 @@ -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug)] -struct Size { - width: u32, - height: u32, -} - -#[derive(Debug)] -struct Rectangle { - size: Size, - name: String, -} - -fn main() { - let mut rect = Rectangle { - size: Size { - width: 30, - height: 50, - }, - name: "MyRect".into(), - }; - - let width_direct = KeyPath::new(|r: &Rectangle| &r.size.width); - println!("Width: {:?}", width_direct.get(&rect)); -} diff --git a/examples/readable_keypaths_new_containers_test.rs b/examples/readable_keypaths_new_containers_test.rs deleted file mode 100644 index ba196fe..0000000 --- a/examples/readable_keypaths_new_containers_test.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! Example demonstrating functional keypath chains for Arc> and Arc> -//! -//! This example shows how to use the chain_arc_mutex_at_kp and chain_arc_rwlock_at_kp methods -//! to access and modify data through synchronization primitives in a functional style. -//! -//! Run with: cargo run --example readable_keypaths_new_containers_test - -use keypaths_proc::{Kp, ReadableKp, WritableKeypaths}; -use rust_keypaths::KeyPath; -use std::rc::Weak; -use std::sync::Arc; - -#[derive(Debug, Kp)] -struct ContainerTest { - // Error handling containers - result: Result, - result_int: Result, - - // Synchronization primitives - /// Important - it is mandatory to use std::sync::Mutex over Mutex and use std::sync::Mutex; statement - /// as our parser for Keypaths written for parking_lot as default if you want to use std then use with full import syntax - /// - mutex_data: Arc>, - /// Important - it is mandatory to use std::sync::RwLock over RwLock and use std::sync::RwLock; statement - /// as our parser for Keypaths written for parking_lot as default if you want to use std then use with full import syntax - /// - rwlock_data: Arc>, - - // Reference counting with weak references - weak_ref: Weak, - - // Basic types for comparison - name: String, - age: u32, -} - -impl ContainerTest { - fn new() -> Self { - Self { - result: Ok("Success!".to_string()), - result_int: Ok(42), - mutex_data: Arc::new(std::sync::Mutex::new(SomeStruct { - data: "Hello".to_string(), - optional_field: Some("Optional value".to_string()), - })), - rwlock_data: Arc::new(std::sync::RwLock::new(SomeStruct { - data: "RwLock Hello".to_string(), - optional_field: Some("RwLock Optional".to_string()), - })), - weak_ref: Weak::new(), - name: "Akash".to_string(), - age: 30, - } - } -} - -#[derive(Debug, Kp, WritableKeypaths)] -struct SomeStruct { - data: String, - optional_field: Option, -} - -fn main() { - println!("=== Functional Keypath Chains for Arc> and Arc> ===\n"); - - let container = ContainerTest::new(); - - // Test Result with ReadableKeypaths - if let Some(value) = ContainerTest::result_fr().get(&container) { - println!("✅ Result value: {}", value); - } - - // ========================================================== - println!("\n=== Arc> Chain Examples ===\n"); - // ========================================================== - - // Example 1: Read through Arc> with chain_arc_mutex_at_kp - // let x = ContainerTest::rwlock_data_r().to; - let x = ContainerTest::rwlock_data_r().to_arc_rwlock_chain(); - ContainerTest::rwlock_data_fr_at(SomeStruct::data_r()).get(&container, |value| { - println!("asdf = {}", value); - }); - ContainerTest::mutex_data_r() - .chain_arc_mutex_at_kp(SomeStruct::data_r()) - .get(&container, |value| { - println!("✅ chain_arc_mutex_at_kp (read): data = {}", value); - }); - - // Example 2: Read optional field through Arc> - ContainerTest::mutex_data_r() - .chain_arc_mutex_optional_at_kp(SomeStruct::optional_field_fr()) - .get(&container, |value| { - println!( - "✅ chain_arc_mutex_optional_at_kp (read): optional_field = {}", - value - ); - }); - - // Example 3: Write through Arc> - let write_container = ContainerTest::new(); - ContainerTest::mutex_data_r() - .chain_arc_mutex_writable_at_kp(SomeStruct::data_w()) - .get_mut(&write_container, |value| { - *value = "Modified via chain_arc_mutex_writable_at_kp".to_string(); - println!("✅ chain_arc_mutex_writable_at_kp (write): Modified data"); - }); - - // Verify the write - ContainerTest::mutex_data_r() - .chain_arc_mutex_at_kp(SomeStruct::data_r()) - .get(&write_container, |value| { - println!(" Verified: data = {}", value); - }); - - // Example 4: Write optional field through Arc> - ContainerTest::mutex_data_r() - .chain_arc_mutex_writable_optional_at_kp(SomeStruct::optional_field_fw()) - .get_mut(&write_container, |value| { - *value = "Modified optional via chain".to_string(); - println!("✅ chain_arc_mutex_writable_optional_at_kp (write): Modified optional_field"); - }); - - // Verify the write - ContainerTest::mutex_data_r() - .chain_arc_mutex_optional_at_kp(SomeStruct::optional_field_fr()) - .get(&write_container, |value| { - println!(" Verified: optional_field = {}", value); - }); - - // ========================================================== - println!("\n=== Arc> Chain Examples ===\n"); - // ========================================================== - - // Example 5: Read through Arc> with chain_arc_rwlock_at_kp - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_at_kp(SomeStruct::data_r()) - .get(&container, |value| { - println!("✅ chain_arc_rwlock_at_kp (read): data = {}", value); - }); - - // Example 6: Read optional field through Arc> - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_optional_at_kp(SomeStruct::optional_field_fr()) - .get(&container, |value| { - println!( - "✅ chain_arc_rwlock_optional_at_kp (read): optional_field = {}", - value - ); - }); - - // Example 7: Write through Arc> - let rwlock_write_container = ContainerTest::new(); - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_writable_at_kp(SomeStruct::data_w()) - .get_mut(&rwlock_write_container, |value| { - *value = "Modified via chain_arc_rwlock_writable_at_kp".to_string(); - println!("✅ chain_arc_rwlock_writable_at_kp (write): Modified data"); - }); - - // Verify the write - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_at_kp(SomeStruct::data_r()) - .get(&rwlock_write_container, |value| { - println!(" Verified: data = {}", value); - }); - - // Example 8: Write optional field through Arc> - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_writable_optional_at_kp(SomeStruct::optional_field_fw()) - .get_mut(&rwlock_write_container, |value| { - *value = "Modified optional via rwlock chain".to_string(); - println!( - "✅ chain_arc_rwlock_writable_optional_at_kp (write): Modified optional_field" - ); - }); - - // Verify the write - ContainerTest::rwlock_data_r() - .chain_arc_rwlock_optional_at_kp(SomeStruct::optional_field_fr()) - .get(&rwlock_write_container, |value| { - println!(" Verified: optional_field = {}", value); - }); - - // ========================================================== - println!("\n=== Summary ===\n"); - // ========================================================== - - println!("Available chain methods from KeyPath:"); - println!(" • chain_arc_mutex_at_kp(inner_keypath) -> read through Arc>"); - println!( - " • chain_arc_mutex_optional_at_kp(inner_optional_keypath) -> read optional through Arc>" - ); - println!( - " • chain_arc_mutex_writable_at_kp(inner_writable_keypath) -> write through Arc>" - ); - println!( - " • chain_arc_mutex_writable_optional_at_kp(inner_writable_optional_keypath) -> write optional through Arc>" - ); - println!(" • chain_arc_rwlock_at_kp(inner_keypath) -> read through Arc>"); - println!( - " • chain_arc_rwlock_optional_at_kp(inner_optional_keypath) -> read optional through Arc>" - ); - println!( - " • chain_arc_rwlock_writable_at_kp(inner_writable_keypath) -> write through Arc>" - ); - println!( - " • chain_arc_rwlock_writable_optional_at_kp(inner_writable_optional_keypath) -> write optional through Arc>" - ); - - println!("\n=== All functional chain examples completed successfully! ==="); -} diff --git a/examples/readable_keypaths_simple.rs b/examples/readable_keypaths_simple.rs deleted file mode 100644 index bdb711b..0000000 --- a/examples/readable_keypaths_simple.rs +++ /dev/null @@ -1,47 +0,0 @@ -use keypaths_proc::ReadableKeypaths; - -#[derive(Debug, ReadableKeypaths)] -struct Person { - name: String, - age: u32, - email: Option, - hobbies: Vec, - scores: std::collections::HashMap, -} - -fn main() { - let person = Person { - name: "John Doe".to_string(), - age: 25, - email: Some("john@example.com".to_string()), - hobbies: vec!["reading".to_string(), "coding".to_string()], - scores: { - let mut map = std::collections::HashMap::new(); - map.insert("math".to_string(), 95); - map.insert("science".to_string(), 88); - map - }, - }; - - // Basic readable keypaths - println!("Name: {:?}", Person::name_r().get(&person)); - println!("Age: {:?}", Person::age_r().get(&person)); - - // Failable readable keypaths - if let Some(email) = Person::email_fr().get(&person) { - println!("Email: {}", email); - } - - if let Some(hobby) = Person::hobbies_fr().get(&person) { - println!("First hobby: {}", hobby); - } - - if let Some(score) = Person::scores_fr("math".to_string()).get(&person) { - println!("Math score: {}", score); - } - - // Indexed access for Vec - if let Some(hobby) = Person::hobbies_fr_at(1).get(&person) { - println!("Second hobby: {}", hobby); - } -} diff --git a/examples/readable_keypaths_test.rs b/examples/readable_keypaths_test.rs deleted file mode 100644 index 68b9677..0000000 --- a/examples/readable_keypaths_test.rs +++ /dev/null @@ -1,197 +0,0 @@ -use keypaths_proc::ReadableKeypaths; -use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; -use std::rc::Rc; -use std::sync::Arc; - -#[derive(Debug, ReadableKeypaths)] -struct User { - name: String, - age: u32, - email: Option, - tags: Vec, - preferences: HashMap, - friends: HashSet, - scores: BTreeMap, - history: VecDeque, - notes: LinkedList, - priority_queue: BinaryHeap, - profile: Box, - avatar: Rc, - metadata: Arc>, -} - -#[derive(Debug)] -struct UserProfile { - bio: String, - location: String, -} - -#[derive(Debug, ReadableKeypaths)] -struct TupleStruct(String, Option, Vec); - -fn main() { - let user = User { - name: "Akash".to_string(), - age: 30, - email: Some("akash@example.com".to_string()), - tags: vec!["developer".to_string(), "rust".to_string()], - preferences: { - let mut map = HashMap::new(); - map.insert("theme".to_string(), "dark".to_string()); - map.insert("language".to_string(), "en".to_string()); - map - }, - friends: { - let mut set = HashSet::new(); - set.insert("bob".to_string()); - set.insert("charlie".to_string()); - set - }, - scores: { - let mut map = BTreeMap::new(); - map.insert("math".to_string(), 95); - map.insert("science".to_string(), 88); - map - }, - history: { - let mut deque = VecDeque::new(); - deque.push_back("login".to_string()); - deque.push_back("view_profile".to_string()); - deque - }, - notes: { - let mut list = LinkedList::new(); - list.push_back("Important note".to_string()); - list.push_back("Another note".to_string()); - list - }, - priority_queue: { - let mut heap = BinaryHeap::new(); - heap.push(10); - heap.push(5); - heap.push(15); - heap - }, - profile: Box::new(UserProfile { - bio: "Software developer".to_string(), - location: "San Francisco".to_string(), - }), - avatar: Rc::new("avatar.png".to_string()), - metadata: Arc::new({ - let mut map = HashMap::new(); - map.insert("created_at".to_string(), "2024-01-01".to_string()); - map - }), - }; - - // Test basic readable keypaths - println!("=== Basic Readable Keypaths ==="); - let name_path = User::name_r(); - println!("Name: {:?}", name_path.get(&user)); - - let age_path = User::age_r(); - println!("Age: {:?}", age_path.get(&user)); - - // Test failable readable keypaths for Option - println!("\n=== Failable Readable Keypaths (Option) ==="); - let email_path = User::email_fr(); - if let Some(email) = email_path.get(&user) { - println!("Email: {}", email); - } else { - println!("No email found"); - } - - // Test failable readable keypaths for Vec - println!("\n=== Failable Readable Keypaths (Vec) ==="); - let first_tag_path = User::tags_fr(); - if let Some(tag) = first_tag_path.get(&user) { - println!("First tag: {}", tag); - } - - let tag_at_1_path = User::tags_fr_at(1); - if let Some(tag) = tag_at_1_path.get(&user) { - println!("Tag at index 1: {}", tag); - } - - // Test failable readable keypaths for HashMap - println!("\n=== Failable Readable Keypaths (HashMap) ==="); - let theme_path = User::preferences_fr("theme".to_string()); - if let Some(theme) = theme_path.get(&user) { - println!("Theme preference: {}", theme); - } - - // Test failable readable keypaths for HashSet - println!("\n=== Failable Readable Keypaths (HashSet) ==="); - let first_friend_path = User::friends_fr(); - if let Some(friend) = first_friend_path.get(&user) { - println!("First friend: {}", friend); - } - - // Test failable readable keypaths for BTreeMap - println!("\n=== Failable Readable Keypaths (BTreeMap) ==="); - let math_score_path = User::scores_fr("math".to_string()); - if let Some(score) = math_score_path.get(&user) { - println!("Math score: {}", score); - } - - // Test failable readable keypaths for VecDeque - println!("\n=== Failable Readable Keypaths (VecDeque) ==="); - let front_history_path = User::history_fr(); - if let Some(history) = front_history_path.get(&user) { - println!("Front history: {}", history); - } - - // Test failable readable keypaths for LinkedList - println!("\n=== Failable Readable Keypaths (LinkedList) ==="); - let front_note_path = User::notes_fr(); - if let Some(note) = front_note_path.get(&user) { - println!("Front note: {}", note); - } - - // Test failable readable keypaths for BinaryHeap - println!("\n=== Failable Readable Keypaths (BinaryHeap) ==="); - let peek_priority_path = User::priority_queue_fr(); - if let Some(priority) = peek_priority_path.get(&user) { - println!("Peek priority: {}", priority); - } - - // Test Box dereferencing - println!("\n=== Box Dereferencing ==="); - let bio_path = User::profile_r(); - if let Some(profile) = bio_path.to_optional().get(&user) { - println!("Profile bio: {}", profile.bio); - } - - // Test Rc dereferencing - println!("\n=== Rc Dereferencing ==="); - let avatar_path = User::avatar_r(); - if let Some(avatar) = avatar_path.to_optional().get(&user) { - println!("Avatar: {}", avatar); - } - - // Test Arc dereferencing - println!("\n=== Arc Dereferencing ==="); - let metadata_path = User::metadata_r(); - if let Some(metadata) = metadata_path.to_optional().get(&user) { - println!("Metadata keys: {:?}", metadata.keys().collect::>()); - } - - // Test tuple struct - println!("\n=== Tuple Struct ==="); - let tuple = TupleStruct("test".to_string(), Some(42), vec![1.0, 2.0, 3.0]); - - let f0_path = TupleStruct::f0_r(); - println!("Tuple f0: {:?}", f0_path.get(&tuple)); - - let f1_fr_path = TupleStruct::f1_fr(); - if let Some(value) = f1_fr_path.get(&tuple) { - println!("Tuple f1 (Option): {}", value); - } - - let f2_fr_path = TupleStruct::f2_fr(); - if let Some(value) = f2_fr_path.get(&tuple) { - println!("Tuple f2 (Vec first): {}", value); - } - - println!("\n=== All tests completed successfully! ==="); -} diff --git a/examples/reference_test.rs b/examples/reference_test.rs deleted file mode 100644 index 13f0464..0000000 --- a/examples/reference_test.rs +++ /dev/null @@ -1,152 +0,0 @@ -// Test cases for reference keypath support -// Run with: cargo run --example reference_test - -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Clone)] -struct Person { - name: String, - age: u32, -} - -fn main() { - println!("=== Reference KeyPath Tests ===\n"); - - let people = vec![ - Person { - name: "Akash".to_string(), - age: 30, - }, - Person { - name: "Bob".to_string(), - age: 25, - }, - Person { - name: "Charlie".to_string(), - age: 35, - }, - ]; - - // Test 1: Basic get with readable keypath - println!("--- Test 1: get with Readable KeyPath ---"); - let name_path = KeyPath::new(|p: &Person| &p.name); - - let person_refs: Vec<&Person> = people.iter().collect(); - for person_ref in &person_refs { - let name = name_path.get(person_ref); - println!(" Name: {}", name); - assert!(!name.is_empty(), "Name should not be empty"); - } - println!("✓ Test 1 passed\n"); - - // Test 2: get returns correct values - println!("--- Test 2: get Value Correctness ---"); - let age_path = KeyPath::new(|p: &Person| &p.age); - - let first_ref = &people[0]; - let age = age_path.get(first_ref); - println!(" First person age: {}", age); - assert_eq!(*age, 30, "Age should be 30"); - println!("✓ Test 2 passed\n"); - - // Test 3: get with nested references - println!("--- Test 3: Nested References ---"); - let refs_of_refs: Vec<&&Person> = person_refs.iter().collect(); - for ref_ref in &refs_of_refs { - // Need to deref once to get &Person, then use get - let name = name_path.get(*ref_ref); - println!(" Nested ref name: {}", name); - } - println!("✓ Test 3 passed\n"); - - // Test 4: Writable keypaths don't have get() method - println!("--- Test 4: Writable KeyPath (no get method) ---"); - let name_path_w = WritableKeyPath::new(|p: &mut Person| &mut p.name); - - // WritableKeyPath doesn't have get(), only get_mut() - // This test demonstrates that writable paths are for mutation only - println!(" WritableKeyPath only has get_mut(), not get()"); - println!("✓ Test 4 passed\n"); - - // Test 5: get_mut with mutable references - println!("--- Test 5: get_mut with Mutable References ---"); - let mut people_mut = people.clone(); - let name_path_w = WritableKeyPath::new(|p: &mut Person| &mut p.name); - - let person_mut_ref = &mut people_mut[0]; - let name = name_path_w.get_mut(person_mut_ref); - println!(" Original name: {}", name); - *name = "Akash Smith".to_string(); - println!(" Modified name: {}", name); - assert_eq!(name, "Akash Smith"); - println!("✓ Test 5 passed\n"); - - // Test 6: get_ref with failable keypaths - println!("--- Test 6: get_ref with Failable KeyPath ---"); - - #[derive(Debug)] - struct Employee { - name: String, - manager: Option, - } - - let employees = vec![ - Employee { - name: "Akash".to_string(), - manager: Some("Bob".to_string()), - }, - Employee { - name: "Charlie".to_string(), - manager: None, - }, - ]; - - let manager_path = OptionalKeyPath::new(|e: &Employee| e.manager.as_ref()); - let employee_refs: Vec<&Employee> = employees.iter().collect(); - - for emp_ref in &employee_refs { - match manager_path.get(emp_ref) { - Some(manager) => println!(" {} has manager: {}", emp_ref.name, manager), - None => println!(" {} has no manager", emp_ref.name), - } - } - println!("✓ Test 6 passed\n"); - - // Test 7: Comparison between get with different references - println!("--- Test 7: get with Different References ---"); - let owned_person = &people[0]; - let ref_person = &people[0]; - - // Using get with direct reference - let name1 = name_path.get(owned_person); - println!(" get() result: {}", name1); - - // Using get with another reference - let name2 = name_path.get(ref_person); - println!(" get() result: {}", name2); - assert_eq!(name1, name2, "Both should return the same value"); - println!("✓ Test 7 passed\n"); - - // Test 8: Performance consideration demo - println!("--- Test 8: Performance Benefit Demonstration ---"); - let large_collection: Vec = (0..1000) - .map(|i| Person { - name: format!("Person {}", i), - age: 20 + (i % 50), - }) - .collect(); - - // With references (no cloning) - let refs: Vec<&Person> = large_collection.iter().collect(); - let mut count = 0; - for person_ref in &refs { - let age = age_path.get(person_ref); - if *age > 40 { - count += 1; - } - } - println!(" Found {} people over 40 (using references)", count); - println!("✓ Test 8 passed\n"); - - println!("=== All Tests Passed! ==="); -} diff --git a/examples/result_adapter_example.rs b/examples/result_adapter_example.rs deleted file mode 100644 index 7e963c7..0000000 --- a/examples/result_adapter_example.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Example demonstrating the for_result() adapter for KeyPaths -// Run with: cargo run --example result_adapter_example - -use rust_keypaths::{ - EnumKeyPath, KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath, -}; - -#[derive(Debug, Clone)] -struct User { - name: String, - age: u32, - email: Option, -} - -fn main() { - println!("=== Result Adapter Example ===\n"); - - // Create some 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()); - - // ===== Example 1: Basic Result Usage ===== - println!("--- Example 1: Basic Result Usage ---"); - - let ok_result = Ok(user.clone()); - let err_result: Result = Err("User not found".to_string()); - - // Adapt keypaths for Result using EnumKeyPath::for_ok() - // Chain: Result -> User -> field - let name_path_result = EnumKeyPath::for_ok::().then(name_path.to_optional()); - let age_path_result = EnumKeyPath::for_ok::().then(age_path.to_optional()); - let email_path_result = EnumKeyPath::for_ok::().then(email_path); - - // Access data from Ok result - if let Some(name) = name_path_result.get(&ok_result) { - println!(" Name from Ok result: {}", name); - } - - if let Some(&age) = age_path_result.get(&ok_result) { - println!(" Age from Ok result: {}", age); - } - - if let Some(email) = email_path_result.get(&ok_result) { - println!(" Email from Ok result: {}", email); - } - - // Access data from Err result (should return None) - if let Some(_) = name_path_result.get(&err_result) { - println!(" This should not print!"); - } else { - println!(" Name from Err result: None (as expected)"); - } - - println!("✓ Example 1 completed\n"); - - // ===== Example 2: Collection of Results ===== - println!("--- Example 2: Collection of Results ---"); - - let results: Vec> = vec![ - Ok(User { - name: "Bob".to_string(), - age: 25, - email: Some("bob@example.com".to_string()), - }), - Err("Database error".to_string()), - Ok(User { - name: "Charlie".to_string(), - age: 35, - email: None, - }), - Err("Network timeout".to_string()), - Ok(User { - name: "Diana".to_string(), - age: 28, - email: Some("diana@example.com".to_string()), - }), - ]; - - // Extract names from successful results - let successful_names: Vec<&String> = results - .iter() - .filter_map(|result| name_path_result.get(result)) - .collect(); - - println!(" Successful user names: {:?}", successful_names); - - // Calculate average age from successful results - let ages: Vec = results - .iter() - .filter_map(|result| age_path_result.get(result).copied()) - .collect(); - - let avg_age = if !ages.is_empty() { - ages.iter().sum::() as f64 / ages.len() as f64 - } else { - 0.0 - }; - - println!(" Average age from successful results: {:.1}", avg_age); - - // Count users with email addresses - let users_with_email = results - .iter() - .filter(|result| email_path_result.get(result).is_some()) - .count(); - - println!(" Users with email addresses: {}", users_with_email); - println!("✓ Example 2 completed\n"); - - // ===== Example 3: Error Handling Patterns ===== - println!("--- Example 3: Error Handling Patterns ---"); - - let api_results: Vec> = vec![ - Ok(User { - name: "Eve".to_string(), - age: 32, - email: Some("eve@example.com".to_string()), - }), - Err("Invalid JSON"), - Ok(User { - name: "Frank".to_string(), - age: 45, - email: Some("frank@example.com".to_string()), - }), - Err("Rate limit exceeded"), - ]; - - let name_path_clone = KeyPath::new(|u: &User| &u.name); - let name_path_result_str = - EnumKeyPath::for_ok::().then(name_path_clone.to_optional()); - - // Process results with different error types - for (i, result) in api_results.iter().enumerate() { - match name_path_result_str.get(result) { - Some(name) => println!(" User {}: {} (success)", i + 1, name), - None => println!(" User {}: Failed to load (error)", i + 1), - } - } - - println!("✓ Example 3 completed\n"); - - println!("=== All Examples Completed Successfully! ==="); -} diff --git a/examples/simple_for_option_example.rs b/examples/simple_for_option_example.rs deleted file mode 100644 index 3ed9397..0000000 --- a/examples/simple_for_option_example.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Simple example demonstrating the for_option adapter method -// Run with: cargo run --example simple_for_option_example - -use rust_keypaths::{ - KeyPath, OptionalKeyPath, WithContainer, WritableKeyPath, WritableOptionalKeyPath, -}; - -#[derive(Debug, Clone)] -struct User { - name: String, - age: u32, - email: Option, -} - -fn main() { - println!("=== Simple For Option Adapter 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); - - // ===== Example 1: Basic Option Usage ===== - println!("--- Example 1: Basic Option Usage ---"); - - let option_user: Option = Some(user.clone()); - - // Use for_option to create a keypath that works with Option - let name_option_path = name_path.clone().for_option(); - - // Access name from Option - returns Option<&String> - if let Some(name) = name_option_path.get(&option_user) { - println!(" Name from Option: {}", name); - } - - // ===== Example 2: Writable Option Usage ===== - println!("--- Example 2: Writable Option Usage ---"); - - let mut option_user_mut: Option = Some(user.clone()); - - // Use for_option with writable keypath - let name_option_path_w = name_path_w.clone().for_option(); - - // Modify name in Option - if let Some(name) = name_option_path_w.get_mut(&mut option_user_mut) { - *name = "Akash Updated".to_string(); - println!(" Updated name in Option: {}", name); - } - - // ===== Example 3: Failable KeyPath with Option ===== - println!("--- Example 3: Failable KeyPath with Option ---"); - - let option_user_with_email: Option = Some(User { - name: "Bob".to_string(), - age: 25, - email: Some("bob@example.com".to_string()), - }); - - // Use failable keypath with for_option - let email_option_path = email_path.clone().for_option(); - - // Access email from Option - returns Option> - if let Some(email) = email_option_path.get(&option_user_with_email) { - println!(" Email from Option: {}", email); - } else { - println!(" No user in Option"); - } - - // ===== Example 4: None Option Handling ===== - println!("--- Example 4: None Option Handling ---"); - - let none_user: Option = None; - - // Try to access name from None Option - if let Some(name) = name_option_path.get(&none_user) { - println!(" Name from None Option: {}", name); - } else { - println!(" Correctly handled None Option"); - } - - // ===== Example 5: Collection of Options ===== - println!("--- Example 5: Collection of Options ---"); - - let option_users: Vec> = vec![ - Some(User { - name: "Charlie".to_string(), - age: 35, - email: Some("charlie@example.com".to_string()), - }), - None, - Some(User { - name: "Diana".to_string(), - age: 28, - email: None, - }), - ]; - - // Process names from collection of Options - let mut names = Vec::new(); - for option_user in &option_users { - if let Some(name) = name_option_path.get(&option_user) { - names.push(name.clone()); - } - } - println!(" User names from Option collection: {:?}", names); - - // ===== Example 6: Using with_option (No-Clone Approach) ===== - println!("--- Example 6: Using with_option (No-Clone Approach) ---"); - - let option_user_for_with: Option = Some(user.clone()); - - // Use the original keypath with with_option for no-clone access - name_path - .clone() - .with_option(&option_user_for_with, |name| { - println!(" Name from Option (no-clone): {}", name); - }); - - // ===== Example 7: Comparison: for_option vs with_option ===== - println!("--- Example 7: Comparison: for_option vs with_option ---"); - - let option_user_comp: Option = Some(user.clone()); - - // Method 1: for_option + get_ref (creates new keypath type) - println!(" Method 1 - for_option + get_ref:"); - if let Some(name) = name_path.clone().for_option().get(&option_user_comp) { - println!(" Name: {}", name); - } - - // Method 2: with_option (no-clone callback) - println!(" Method 2 - with_option (no-clone):"); - name_path.clone().with_option(&option_user_comp, |name| { - println!(" Name: {}", name); - }); - - println!("=== All Examples Completed Successfully! ==="); -} diff --git a/examples/simple_mutex_example.rs b/examples/simple_mutex_example.rs deleted file mode 100644 index e70d628..0000000 --- a/examples/simple_mutex_example.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Simple example demonstrating the for_mutex() adapter for KeyPaths -// Run with: cargo run --example simple_mutex_example - -use rust_keypaths::{ - KeyPath, OptionalKeyPath, WithContainer, WritableKeyPath, WritableOptionalKeyPath, -}; -use std::sync::Mutex; - -#[derive(Debug, Clone)] -struct User { - name: String, - age: u32, -} - -fn main() { - println!("=== Simple Mutex Adapter Example ===\n"); - - // Create some test data - let user = User { - name: "Akash".to_string(), - age: 30, - }; - - // Create keypaths - let name_path = KeyPath::new(|u: &User| &u.name); - let age_path = KeyPath::new(|u: &User| &u.age); - - // ===== Example 1: Basic Mutex Usage ===== - println!("--- Example 1: Basic Mutex Usage ---"); - - let mutex_user = Mutex::new(user.clone()); - - // Note: We'll create the adapted keypaths inline since they can't be cloned - - // Access data from Mutex using the no-clone approach - name_path.clone().with_mutex(&mutex_user, |name| { - println!(" Name from Mutex: {}", name); - }); - - // ===== Example 2: Collection of Mutex ===== - println!("--- Example 2: Collection of Mutex ---"); - - let mutex_users: Vec> = vec![ - Mutex::new(User { - name: "Bob".to_string(), - age: 25, - }), - Mutex::new(User { - name: "Charlie".to_string(), - age: 35, - }), - Mutex::new(User { - name: "Diana".to_string(), - age: 28, - }), - ]; - - // Extract names from Mutex collection using the no-clone approach - let mut successful_names = Vec::new(); - for mutex_user in mutex_users { - name_path.clone().with_mutex(&mutex_user, |name| { - successful_names.push(name.clone()); // Only clone when we need to store - }); - } - - println!(" Successful user names: {:?}", successful_names); - - println!("=== Example Completed Successfully! ==="); -} diff --git a/examples/simple_ref_support_example.rs b/examples/simple_ref_support_example.rs deleted file mode 100644 index dc74bd8..0000000 --- a/examples/simple_ref_support_example.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Demonstrates the new reference support methods -// This example shows how to work with collections of references using keypaths -// cargo run --example simple_ref_support_example - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Clone, Kp)] -#[All] -struct Person { - name: String, - age: u32, - email: String, -} - -fn main() { - println!("=== Simple Reference Support Example ===\n"); - - let person1 = Person { - name: "Akash Johnson".to_string(), - age: 30, - email: "akash@example.com".to_string(), - }; - - let person2 = Person { - name: "Bob Smith".to_string(), - age: 25, - email: "bob@example.com".to_string(), - }; - - let person3 = Person { - name: "Charlie Brown".to_string(), - age: 35, - email: "charlie@example.com".to_string(), - }; - - // Example 1: Working with collections of references - println!("--- Example 1: Collections of References ---"); - let people_refs = vec![&person1, &person2, &person3]; - - // Extract names using the new extract_from_ref_slice method - let name_path = Person::name_r(); - let names: Vec<&String> = name_path.extract_from_ref_slice(&people_refs); - - println!(" Names from references:"); - for name in &names { - println!(" • {}", name); - } - - // Example 2: Working with collections of mutable references - println!("\n--- Example 2: Collections of Mutable References ---"); - let mut person1_mut = person1.clone(); - let mut person2_mut = person2.clone(); - let mut person3_mut = person3.clone(); - - let mut people_mut_refs = vec![&mut person1_mut, &mut person2_mut, &mut person3_mut]; - - // Extract mutable names using the new extract_mut_from_ref_slice method - let name_mut_path = Person::name_w(); - let names_mut: Vec<&mut String> = - name_mut_path.extract_mut_from_ref_slice(&mut people_mut_refs); - - // Modify the names - for name in names_mut { - name.push_str(" (Modified)"); - } - - println!(" Modified names:"); - println!(" • {}", person1_mut.name); - println!(" • {}", person2_mut.name); - println!(" • {}", person3_mut.name); - - // Example 3: Working with existing extract_from_slice method - println!("\n--- Example 3: Existing extract_from_slice Method ---"); - let people_owned = vec![person1.clone(), person2.clone(), person3.clone()]; - - // Extract ages using the existing method - let age_path = Person::age_r(); - let ages: Vec<&u32> = age_path.extract_from_slice(&people_owned); - - println!(" Ages from owned values:"); - for age in &ages { - println!(" • {}", age); - } - - // Example 4: Working with existing extract_mut_from_slice method - println!("\n--- Example 4: Existing extract_mut_from_slice Method ---"); - let mut people_owned_mut = vec![person1.clone(), person2.clone(), person3.clone()]; - - // Extract mutable ages using the existing method - let age_mut_path = Person::age_w(); - let ages_mut: Vec<&mut u32> = age_mut_path.extract_mut_from_slice(&mut people_owned_mut); - - // Modify the ages - for age in ages_mut { - *age += 1; - } - - println!(" Modified ages:"); - for person in &people_owned_mut { - println!(" • {}: {}", person.name, person.age); - } - - // Example 5: Comparison of different approaches - println!("\n--- Example 5: Comparison of Approaches ---"); - - // Direct access (traditional approach) - let direct_names: Vec<&String> = people_refs.iter().map(|p| &p.name).collect(); - println!(" Direct access: {:?}", direct_names); - - // KeyPath approach (new approach) - let keypath_names: Vec<&String> = name_path.extract_from_ref_slice(&people_refs); - println!(" KeyPath approach: {:?}", keypath_names); - - println!("\n✅ Simple reference support example completed!"); - println!("📝 Note: The new methods provide a clean way to work with collections of references"); - println!(" without needing to create KeyPaths<&Root, Value> types directly."); -} diff --git a/examples/simple_working_test.rs b/examples/simple_working_test.rs deleted file mode 100644 index 6ea33ab..0000000 --- a/examples/simple_working_test.rs +++ /dev/null @@ -1,30 +0,0 @@ -use keypaths_proc::Kp; - -#[derive(Debug, Kp)] -struct SimpleTest { - // Basic types that should work - string_field: String, - int_field: i32, - bool_field: bool, - - // Simple containers - option_string: Option, - vec_string: Vec, - box_string: Box, -} - -fn main() { - println!("Simple working test"); - - // Test basic types - let _string_path = SimpleTest::string_field_r(); - let _int_path = SimpleTest::int_field_r(); - let _bool_path = SimpleTest::bool_field_r(); - - // Test simple containers - let _option_path = SimpleTest::option_string_fr(); - let _vec_path = SimpleTest::vec_string_r(); - let _box_path = SimpleTest::box_string_r(); - - println!("All simple keypaths generated successfully!"); -} diff --git a/examples/surprise.rs b/examples/surprise.rs deleted file mode 100644 index f584205..0000000 --- a/examples/surprise.rs +++ /dev/null @@ -1,174 +0,0 @@ -use keypaths_proc::{Casepaths, Kp}; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Kp)] -#[All] -struct Profile { - display_name: String, - age: u32, -} - -#[derive(Debug, Kp)] -#[All] -struct User { - id: u64, - profile: Option, - tags: Vec, -} - -#[derive(Debug, Kp)] -#[All] -struct DbConfig(u16, String); // (port, url) - -#[derive(Debug, Kp)] -#[All] -struct Settings { - theme: String, - db: Option, -} - -#[derive(Debug, Casepaths)] -#[All] -enum Connection { - Disconnected, - Connecting(u32), - Connected(String), -} - -#[derive(Debug, Casepaths)] -#[All] -enum Status { - Active(User), - Inactive, - Pending(u32), -} - -#[derive(Debug, Kp)] -#[All] -struct App { - users: Vec, - settings: Option, - connection: Connection, - name: String, -} - -fn main() { - let mut app = App { - users: vec![ - User { - id: 1, - profile: Some(Profile { - display_name: "Ada".into(), - age: 31, - }), - tags: vec!["admin".into(), "founder".into()], - }, - User { - id: 2, - profile: None, - tags: vec!["guest".into()], - }, - ], - settings: Some(Settings { - theme: "dark".into(), - db: Some(DbConfig(5432, "postgres://localhost".into())), - }), - connection: Connection::Connecting(42), - name: "MegaApp".into(), - }; - - // 1) Read a nested optional field via failable readable compose - let first_user_profile_name = App::users_r() - .to_optional() - .then(OptionalKeyPath::new(|v: &Vec| v.first())) - .then(User::profile_fr()) - .then(Profile::display_name_r().to_optional()); - println!( - "first_user_profile_name = {:?}", - first_user_profile_name.get(&app) - ); - - // 2) Mutate nested Option chain via failable writable - let settings_fw = App::settings_fw(); - let db_fw = Settings::db_fw(); - let db_port_w = DbConfig::f0_w(); - if let Some(settings) = settings_fw.get_mut(&mut app) { - if let Some(db) = db_fw.get_mut(settings) { - let port = db_port_w.get_mut(db); - *port += 1; - } - } - println!( - "db after bump = {:?}", - app.settings.as_ref().and_then(|s| s.db.as_ref()) - ); - - // 3) Compose writable + enum case (prism) to mutate only when connected - app.connection = Connection::Connected("10.0.0.1".into()); - let connected_case = Connection::connected_w(); - // compose requires a keypath from App -> Connection first - let app_connection_w = App::connection_w().to_optional(); - let app_connected_ip = app_connection_w.then(connected_case); - if let Some(ip) = app_connected_ip.get_mut(&mut app) { - ip.push_str(":8443"); - } - println!("app.connection = {:?}", app.connection); - - // 4) Enum readable case path for state without payload - app.connection = Connection::Disconnected; - // Unit variants don't have case methods - check directly - match app.connection { - Connection::Disconnected => println!("is disconnected? true"), - _ => println!("is disconnected? false"), - } - - // 5) Iterate immutably and mutably via derived vec keypaths - let users_r = App::users_r(); - if let Some(mut iter) = users_r.iter::(&app) { - if let Some(u0) = iter.next() { - println!("first user id = {}", u0.id); - } - } - let users_w = App::users_w(); - if let Some(iter) = users_w.iter_mut::(&mut app) { - for u in iter { - u.tags.push("seen".into()); - } - } - println!("users after tag = {:?}", app.users); - - // 6) Compose across many levels: first user -> profile -> age (if present) and increment - let first_user_fr = OptionalKeyPath::new(|v: &Vec| v.first()); - let profile_fr = User::profile_fr(); - let age_w = Profile::age_w(); - if let Some(_u0) = first_user_fr.get(&app.users) { - // borrow helper - if let Some(profile) = app.users[0].profile.as_mut() { - let age = age_w.get_mut(profile); - *age += 1; - } - } - println!("first user after bday = {:?}", app.users.first()); - - // 7) Embed: build a Connected from payload - let connected_r = Connection::connected_r(); - let new_conn = Connection::Connected("192.168.0.1".to_string()); - println!("embedded = {:?}", new_conn); - - // 8) Additional enum with casepaths: Status - let mut st = Status::Active(User { - id: 99, - profile: None, - tags: vec![], - }); - let st_active = Status::active_r(); - let st_active_name = st_active.then(User::id_r().to_optional()); - println!("status active user id = {:?}", st_active_name.get(&st)); - - let st_pending = Status::pending_w(); - st = Status::Pending(5); - if let Some(v) = st_pending.get_mut(&mut st) { - *v += 1; - } - println!("status after pending increment = {:?}", st); -} diff --git a/examples/tokio_containers.rs b/examples/tokio_containers.rs deleted file mode 100644 index 4054b0d..0000000 --- a/examples/tokio_containers.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! Example demonstrating Tokio keypath chains with Arc> and Arc> -//! -//! This example shows how to use keypaths with Tokio's async synchronization primitives. -//! Tokio locks are async, so all operations must be awaited. -use keypaths_proc::Kp; -/// cargo run --example tokio_containers 2>&1 -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath}; -use std::sync::Arc; -use tokio::sync::{Mutex, RwLock}; - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct AppState { - user_data: Arc>, - config: Arc>, - optional_cache: Option>>, - optional_mutex_cache: Option>>, -} - -impl Clone for AppState { - fn clone(&self) -> Self { - panic!("AppState::clone() should never be called") - } -} - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct UserData { - name: String, - email: String, - settings: UserSettings, -} - -impl Clone for UserData { - fn clone(&self) -> Self { - panic!("UserData::clone() should never be called") - } -} - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct UserSettings { - theme: String, - notifications: bool, -} - -impl Clone for UserSettings { - fn clone(&self) -> Self { - panic!("UserSettings::clone() should never be called") - } -} - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct Config { - api_key: String, - timeout: u64, - features: FeatureFlags, -} - -impl Clone for Config { - fn clone(&self) -> Self { - panic!("Config::clone() should never be called") - } -} - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct FeatureFlags { - enable_logging: bool, - enable_metrics: bool, -} - -impl Clone for FeatureFlags { - fn clone(&self) -> Self { - panic!("FeatureFlags::clone() should never be called") - } -} - -#[derive(Kp, Debug)] -#[All] // Generate all methods (readable, writable, owned) -struct Cache { - entries: Vec, - size: usize, -} - -impl Clone for Cache { - fn clone(&self) -> Self { - panic!("Cache::clone() should never be called") - } -} - -#[tokio::main] -async fn main() { - println!("=== Tokio Keypath Chains Example ===\n"); - - // Create initial state - let state = AppState { - user_data: Arc::new(tokio::sync::Mutex::new(UserData { - name: "Akash".to_string(), - email: "alice@example.com".to_string(), - settings: UserSettings { - theme: "dark".to_string(), - notifications: true, - }, - })), - config: Arc::new(tokio::sync::RwLock::new(Config { - api_key: "secret-key-123".to_string(), - timeout: 30, - features: FeatureFlags { - enable_logging: true, - enable_metrics: false, - }, - })), - optional_cache: Some(Arc::new(tokio::sync::RwLock::new(Cache { - entries: vec!["entry1".to_string(), "entry2".to_string()], - size: 2, - }))), - optional_mutex_cache: Some(Arc::new(tokio::sync::Mutex::new(Cache { - entries: vec![ - "mutex_entry1".to_string(), - "mutex_entry2".to_string(), - "mutex_entry3".to_string(), - ], - size: 3, - }))), - }; - - // Example 1: Reading through Arc> using proc macro _fr_at() method - println!("1. Reading through Arc> using proc macro:"); - AppState::user_data_fr_at(UserData::name_r()) - .get(&state, |name| { - println!(" User name: {}", name); - }) - .await; - - // Example 2: Reading nested fields through Arc> using proc macro - println!("\n2. Reading nested fields through Arc> using proc macro:"); - AppState::user_data_fr_at(UserData::settings_r()) - .then(UserSettings::theme_r()) - .get(&state, |theme| { - println!(" User theme: {}", theme); - }) - .await; - - // Example 3: Writing through Arc> using proc macro _fw_at() method - println!("\n3. Writing through Arc> using proc macro:"); - AppState::user_data_fw_at(UserData::name_w()) - .get_mut(&state, |name| { - *name = "Bob".to_string(); - println!(" Updated user name to: {}", name); - }) - .await; - - // Verify the change - AppState::user_data_fr_at(UserData::name_r()) - .get(&state, |name| { - println!(" Verified name is now: {}", name); - }) - .await; - - // Example 4: Reading through Arc> (read lock) using proc macro - println!("\n4. Reading through Arc> (read lock) using proc macro:"); - AppState::config_fr_at(Config::api_key_r()) - .get(&state, |api_key| { - println!(" API key: {}", api_key); - }) - .await; - - // Example 5: Reading nested fields through Arc> using proc macro - println!("\n5. Reading nested fields through Arc> using proc macro:"); - AppState::config_fr_at(Config::features_r()) - .then(FeatureFlags::enable_logging_r()) - .get(&state, |enable_logging| { - println!(" Enable logging: {}", enable_logging); - }) - .await; - - // Example 6: Writing through Arc> (write lock) using proc macro - println!("\n6. Writing through Arc> (write lock) using proc macro:"); - AppState::config_fw_at(Config::timeout_w()) - .get_mut(&state, |timeout| { - *timeout = 60; - println!(" Updated timeout to: {}", timeout); - }) - .await; - - // Verify the change - AppState::config_fr_at(Config::timeout_r()) - .get(&state, |timeout| { - println!(" Verified timeout is now: {}", timeout); - }) - .await; - - // Example 7: Reading through optional Arc> using proc macro - println!("\n7. Reading through optional Arc> using proc macro:"); - if let Some(()) = AppState::optional_cache_fr() - .chain_arc_tokio_rwlock_at_kp(Cache::size_r()) - .get(&state, |size| { - println!(" Cache size: {}", size); - }) - .await - { - println!(" Successfully read cache size"); - } else { - println!(" Cache is None"); - } - - // Example 8: Writing through optional Arc> using proc macro - println!("\n8. Writing through optional Arc> using proc macro:"); - if let Some(()) = AppState::optional_cache_fr() - .chain_arc_tokio_rwlock_writable_at_kp(Cache::size_w()) - .get_mut(&state, |size| { - *size = 100; - println!(" Updated cache size to: {}", size); - }) - .await - { - println!(" Successfully updated cache size"); - } else { - println!(" Cache is None"); - } - - // Example 9: Chaining multiple levels through Tokio Mutex using proc macro - println!("\n9. Chaining multiple levels through Tokio Mutex using proc macro:"); - AppState::user_data_fr_at(UserData::settings_r()) - .then(UserSettings::notifications_r()) - .get(&state, |notifications| { - println!(" Notifications enabled: {}", notifications); - }) - .await; - - // Example 10: Chaining multiple levels through Tokio RwLock using proc macro - println!("\n10. Chaining multiple levels through Tokio RwLock using proc macro:"); - AppState::config_fr_at(Config::features_r()) - .then(FeatureFlags::enable_metrics_r()) - .get(&state, |enable_metrics| { - println!(" Enable metrics: {}", enable_metrics); - }) - .await; - - // Example 11: Writing nested fields through Tokio RwLock using proc macro - println!("\n11. Writing nested fields through Tokio RwLock using proc macro:"); - AppState::config_fw_at(Config::features_w()) - .then(FeatureFlags::enable_metrics_w()) - .get_mut(&state, |enable_metrics| { - *enable_metrics = true; - println!(" Updated enable_metrics to: {}", enable_metrics); - }) - .await; - - // Verify the change - AppState::config_fr_at(Config::features_r()) - .then(FeatureFlags::enable_metrics_r()) - .get(&state, |enable_metrics| { - println!(" Verified enable_metrics is now: {}", enable_metrics); - }) - .await; - - // Example 12: Reading through optional Arc> using proc macro - println!("\n12. Reading through optional Arc> using proc macro:"); - if let Some(()) = AppState::optional_mutex_cache_fr() - .chain_arc_tokio_mutex_at_kp(Cache::size_r()) - .get(&state, |size| { - println!(" Mutex cache size: {}", size); - }) - .await - { - println!(" Successfully read mutex cache size"); - } else { - println!(" Mutex cache is None"); - } - - // Example 13: Writing through optional Arc> using proc macro - println!("\n13. Writing through optional Arc> using proc macro:"); - if let Some(()) = AppState::optional_mutex_cache_fr() - .chain_arc_tokio_mutex_writable_at_kp(Cache::size_w()) - .get_mut(&state, |size| { - *size = 200; - println!(" Updated mutex cache size to: {}", size); - }) - .await - { - println!(" Successfully updated mutex cache size"); - } else { - println!(" Mutex cache is None"); - } - - // Example 14: Reading nested fields through optional Arc> - println!("\n14. Reading nested fields through optional Arc>:"); - if let Some(()) = AppState::optional_mutex_cache_fr() - .chain_arc_tokio_mutex_at_kp(Cache::entries_r()) - .get(&state, |entries| { - println!(" Mutex cache entries count: {}", entries.len()); - if let Some(first) = entries.first() { - println!(" First entry: {}", first); - } - }) - .await - { - println!(" Successfully read mutex cache entries"); - } else { - println!(" Mutex cache is None"); - } - - println!("\n=== Example Complete ==="); -} diff --git a/examples/tokio_simple.rs b/examples/tokio_simple.rs deleted file mode 100644 index 40035be..0000000 --- a/examples/tokio_simple.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Simple example demonstrating Tokio RwLock with keypaths -//! -//! Run with: cargo run --example tokio_simple --features tokio -//! -//! This example shows how to: -//! 1. Use derive-generated keypath methods for lock fields -//! 2. Read and write through a single Arc> -//! 3. Chain keypaths through the lock to access inner fields (async) - -// #[cfg(not(feature = "tokio"))] -// compile_error!("This example requires the 'tokio' feature. Run with: cargo run --example tokio_simple --features tokio"); - -#[cfg(feature = "tokio")] -mod example { - use keypaths_proc::Kp; - use std::sync::Arc; - - #[derive(Kp)] - #[All] // Generate all methods (readable, writable, owned) - pub struct AppState { - pub user_data: Arc>, - } - - impl Clone for AppState { - fn clone(&self) -> Self { - panic!("AppState should not be cloned!") - } - } - - #[derive(Kp)] - #[All] // Generate all methods (readable, writable, owned) - pub struct UserData { - pub name: String, - pub age: u32, - pub email: Option, - } - - impl Clone for UserData { - fn clone(&self) -> Self { - panic!("UserData should not be cloned!") - } - } - - pub async fn run() { - println!("=== Simple Tokio RwLock Example ===\n"); - - let state = AppState { - user_data: Arc::new(tokio::sync::RwLock::new(UserData { - name: String::from("Akash"), - age: 30, - email: Some(String::from("alice@example.com")), - })), - }; - - println!("Initial state:"); - { - let guard = state.user_data.read().await; - println!(" name = {:?}", guard.name); - println!(" age = {:?}", guard.age); - println!(" email = {:?}", guard.email); - } - - println!("\n=== Using Generated Keypath Methods ==="); - println!("For Arc> fields, the macro generates:"); - println!(" • _r() -> KeyPath>> (readable)"); - println!(" • _w() -> WritableKeyPath>> (writable)"); - println!(" • _fr_at() -> Chain through lock for reading (tokio, async)"); - println!(" • _fw_at() -> Chain through lock for writing (tokio, async)\n"); - - // ============================================================ - // READING THROUGH THE LOCK (async) - // ============================================================ - println!("--- Reading through lock (async) ---"); - - // Read name field through the lock - AppState::user_data_fr_at(UserData::name_r()) - .get(&state, |name| { - println!("✅ Read name via keypath: {:?}", name); - }) - .await; - - // Read age field through the lock - AppState::user_data_fr_at(UserData::age_r()) - .get(&state, |age| { - println!("✅ Read age via keypath: {:?}", age); - }) - .await; - - // Read optional email field through the lock - AppState::user_data_fr_at(UserData::email_r()) - .get(&state, |email| { - println!("✅ Read email via keypath: {:?}", email); - }) - .await; - - // ============================================================ - // WRITING THROUGH THE LOCK (async) - // ============================================================ - println!("\n--- Writing through lock (async) ---"); - - // Write to name field - AppState::user_data_fw_at(UserData::name_w()) - .get_mut(&state, |name| { - *name = String::from("Bob"); - println!("✅ Updated name to: {:?}", name); - }) - .await; - - // Write to age field - AppState::user_data_fw_at(UserData::age_w()) - .get_mut(&state, |age| { - *age = 35; - println!("✅ Updated age to: {:?}", age); - }) - .await; - - // Write to optional email field - AppState::user_data_fw_at(UserData::email_w()) - .get_mut(&state, |email| { - *email = Some(String::from("bob@example.com")); - println!("✅ Updated email to: {:?}", email); - }) - .await; - - // ============================================================ - // VERIFY THE WRITES - // ============================================================ - println!("\n--- Final state ---"); - { - let guard = state.user_data.read().await; - println!(" name = {:?}", guard.name); - println!(" age = {:?}", guard.age); - println!(" email = {:?}", guard.email); - } - - println!("\n=== Key Takeaways ==="); - println!("✅ No cloning occurred - all access was zero-copy!"); - println!( - "✅ Used derive-generated keypaths: AppState::user_data_fr_at(), AppState::user_data_fw_at()" - ); - println!("✅ Chained through Arc> safely"); - println!("✅ All operations are async and must be awaited"); - println!("✅ Works with Tokio for async runtime compatibility"); - } -} - -#[cfg(feature = "tokio")] -#[tokio::main] -async fn main() { - example::run().await; -} - -#[cfg(not(feature = "tokio"))] -fn main() { - eprintln!("This example requires the 'tokio' feature."); - eprintln!("Run with: cargo run --example tokio_simple --features tokio"); -} diff --git a/examples/tuple_struct_macros.rs b/examples/tuple_struct_macros.rs deleted file mode 100644 index 77d752e..0000000 --- a/examples/tuple_struct_macros.rs +++ /dev/null @@ -1,29 +0,0 @@ -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; - -#[derive(Debug, Kp)] -#[All] -struct Point(u32, Option, String); - -fn main() { - let mut p = Point(10, Some(20), "name".into()); - // Non-Option fields - let x_r = Point::f0_r(); - let name_w = Point::f2_w(); - println!("x = {:?}", x_r.get(&p)); - let n = name_w.get_mut(&mut p); - { - n.push_str("_edited"); - } - - // Option field with failable - let y_fr = Point::f1_fr(); - println!("y (fr) = {:?}", y_fr.get(&p)); - - let y_fw = Point::f1_fw(); - if let Some(y) = y_fw.get_mut(&mut p) { - *y += 1; - } - - println!("updated p = {:?}", p); -} diff --git a/examples/type_safety_demo.rs b/examples/type_safety_demo.rs deleted file mode 100644 index fc76051..0000000 --- a/examples/type_safety_demo.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Example demonstrating type safety when composing keypaths -//! -//! This example shows what happens when you try to compose keypaths -//! that don't share the same root type - it fails at compile time! - -use keypaths_proc::Kp; - -#[derive(Kp, Debug)] -#[All] -struct Person { - name: String, - age: u32, - address: Address, -} - -#[derive(Kp, Debug)] -#[All] -struct Address { - street: String, - city: String, -} - -#[derive(Kp, Debug)] -#[All] -struct Company { - name: String, - employees: Vec, -} - -#[derive(Kp, Debug)] -#[All] -struct Product { - name: String, - price: f64, -} - -fn main() { - let person = Person { - name: "Akash".to_string(), - age: 30, - address: Address { - street: "123 Main St".to_string(), - city: "New York".to_string(), - }, - }; - - // ✅ CORRECT: Chaining keypaths that share the same root - // Person -> Address -> city (all part of the same type hierarchy) - let city_kp = Person::address_r().then(Address::city_r()); - - println!("City: {}", city_kp.get(&person)); - - // ❌ COMPILE ERROR: Trying to chain keypaths from different roots - // Person::name_r() returns KeyPath - // Product::name_r() expects Product as root, not String! - - // Uncomment the following to see the compile error: - /* - let invalid_kp = Person::name_r() - .then(Product::name_r()); // ERROR: expected `String`, found `Product` - */ - - // ❌ COMPILE ERROR: Trying to use a keypath from a completely different struct - // Person::age_r() returns KeyPath - // Company::name_r() expects Company as root, not u32! - - // Uncomment the following to see the compile error: - /* - let invalid_kp2 = Person::age_r() - .then(Company::name_r()); // ERROR: expected `u32`, found `Company` - */ - - // ❌ COMPILE ERROR: Type mismatch in the chain - // Person::address_r() returns KeyPath - // Person::name_r() expects Person as root, not Address! - - // Uncomment the following to see the compile error: - /* - let invalid_kp3 = Person::address_r() - .then(Person::name_r()); // ERROR: expected `Address`, found `Person` - */ - - println!("\n✅ All valid keypath compositions compiled successfully!"); - println!("❌ Invalid compositions would fail at compile time with clear error messages."); - println!("\nThe Rust compiler ensures type safety:"); - println!(" - The Value type of the first keypath must match the Root type of the second"); - println!(" - This prevents runtime errors and ensures correctness"); - println!("\n📝 Example compile errors you would see:"); - println!(" error[E0308]: mismatched types"); - println!(" --> expected `String`"); - println!(" --> found `Product`"); - println!("\n This happens because:"); - println!(" - Person::name_r() returns KeyPath"); - println!(" - Product::name_r() expects KeyPath"); - println!(" - When chaining, the Value (String) must match the Root (Product)"); - println!(" - Since String ≠ Product, the compiler rejects it"); -} diff --git a/examples/type_safety_error_demo.rs b/examples/type_safety_error_demo.rs deleted file mode 100644 index f0f8deb..0000000 --- a/examples/type_safety_error_demo.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Example demonstrating compile-time errors when composing incompatible keypaths -//! -//! This file intentionally contains compile errors to show what happens -//! when you try to compose keypaths from different root types. -//! -//! To see the errors, uncomment the marked sections below. - -use keypaths_proc::Kp; - -#[derive(Kp, Debug)] -#[All] -struct Person { - name: String, - age: u32, -} - -#[derive(Kp, Debug)] -#[All] -struct Product { - name: String, - price: f64, -} - -fn main() { - // ✅ CORRECT: This works because both keypaths share the same root - // Person::name_r() returns KeyPath - // We can't chain further because String doesn't have keypaths - - // ❌ ERROR 1: Trying to chain keypaths from completely different structs - // Person::name_r() returns KeyPath - // Product::name_r() expects Product as root, not String! - // - // Uncomment to see error: - // let invalid_kp = Person::name_r() - // .then(Product::name_r()); - // Error: expected `String`, found `Product` - // expected struct `String` - // found struct `Product` - - // ❌ ERROR 2: Type mismatch - trying to use a keypath that expects - // a different type than what the first keypath produces - // Person::age_r() returns KeyPath - // Product::name_r() expects Product, not u32! - // - // Uncomment to see error: - // let invalid_kp2 = Person::age_r() - // .then(Product::name_r()); - // Error: expected `u32`, found `Product` - // expected `u32` - // found struct `Product` - - println!("This example demonstrates compile-time type safety."); - println!("Uncomment the error cases above to see the compiler errors."); -} diff --git a/examples/undo_redo.rs b/examples/undo_redo.rs deleted file mode 100644 index b21939a..0000000 --- a/examples/undo_redo.rs +++ /dev/null @@ -1,457 +0,0 @@ -// Demonstrates implementing undo/redo functionality using keypaths -// This example shows how to: -// 1. Track changes to deeply nested data structures -// 2. Implement command pattern for undo/redo -// 3. Handle multiple field types in undo/redo -// 4. Support redo after undo operations -// 5. Display history of changes -// cargo run --example undo_redo - -use keypaths_proc::Kp; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use std::rc::Rc; - -#[derive(Debug, Clone, Kp)] -#[All] -struct Document { - title: String, - content: String, - metadata: DocumentMetadata, -} - -#[derive(Debug, Clone, Kp)] -#[All] -struct DocumentMetadata { - author: String, - tags: Vec, - revision: u32, -} - -// Generic command pattern using keypaths -struct ChangeCommand { - path: Box Option<&mut F>>, - old_value: F, - new_value: F, - description: String, -} - -impl ChangeCommand { - fn execute(&self, target: &mut T) { - if let Some(field) = (self.path)(target) { - *field = self.new_value.clone(); - } - } - - fn undo(&self, target: &mut T) { - if let Some(field) = (self.path)(target) { - *field = self.old_value.clone(); - } - } -} - -// Trait for commands that can be executed and undone -trait Command { - fn execute(&self, target: &mut T); - fn undo(&self, target: &mut T); - fn description(&self) -> &str; -} - -impl Command for ChangeCommand { - fn execute(&self, target: &mut T) { - ChangeCommand::execute(self, target) - } - - fn undo(&self, target: &mut T) { - ChangeCommand::undo(self, target) - } - - fn description(&self) -> &str { - &self.description - } -} - -// Undo/Redo stack manager -struct UndoStack { - commands: Vec>>, - current: usize, // Points to the next position to add a command -} - -impl UndoStack { - fn new() -> Self { - Self { - commands: Vec::new(), - current: 0, - } - } - - // Execute a new command and add it to the stack - fn execute(&mut self, target: &mut T, command: Box>) { - // Execute the command - command.execute(target); - - // If we're not at the end, truncate the redo history - if self.current < self.commands.len() { - self.commands.truncate(self.current); - } - - // Add the command to the stack - self.commands.push(command); - self.current += 1; - } - - // Undo the last command - fn undo(&mut self, target: &mut T) -> Result { - if self.current == 0 { - return Err("Nothing to undo".into()); - } - - self.current -= 1; - let command = &self.commands[self.current]; - let desc = command.description().to_string(); - command.undo(target); - Ok(desc) - } - - // Redo the last undone command - fn redo(&mut self, target: &mut T) -> Result { - if self.current >= self.commands.len() { - return Err("Nothing to redo".into()); - } - - let command = &self.commands[self.current]; - let desc = command.description().to_string(); - command.execute(target); - self.current += 1; - Ok(desc) - } - - // Check if undo is available - fn can_undo(&self) -> bool { - self.current > 0 - } - - // Check if redo is available - fn can_redo(&self) -> bool { - self.current < self.commands.len() - } - - // Get the history of commands - fn history(&self) -> Vec { - self.commands - .iter() - .enumerate() - .map(|(i, cmd)| { - let marker = if i < self.current { "✓" } else { " " }; - format!("{} {}", marker, cmd.description()) - }) - .collect() - } -} - -// Helper to create change commands for strings -fn make_string_change( - target: &T, - path: WritableOptionalKeyPath, - read_path: OptionalKeyPath, - new_value: String, - description: String, -) -> Box> -where - P: for<'r> Fn(&'r mut T) -> Option<&'r mut String> + 'static, - R: for<'r> Fn(&'r T) -> Option<&'r String> + 'static, -{ - let old_value = read_path.get(target).map(|s| s.clone()).unwrap_or_default(); - let path_rc = Rc::new(path); - let path_clone = path_rc.clone(); - let path_box: Box Option<&mut String>> = - Box::new(move |t: &mut T| path_clone.get_mut(t)); - Box::new(ChangeCommand { - path: path_box, - old_value, - new_value, - description, - }) -} - -// Helper to create change commands for u32 -fn make_u32_change( - target: &T, - path: WritableOptionalKeyPath, - read_path: OptionalKeyPath, - new_value: u32, - description: String, -) -> Box> -where - P: for<'r> Fn(&'r mut T) -> Option<&'r mut u32> + 'static, - R: for<'r> Fn(&'r T) -> Option<&'r u32> + 'static, -{ - let old_value = read_path.get(target).copied().unwrap_or_default(); - let path_rc = Rc::new(path); - let path_clone = path_rc.clone(); - let path_box: Box Option<&mut u32>> = - Box::new(move |t: &mut T| path_clone.get_mut(t)); - Box::new(ChangeCommand { - path: path_box, - old_value, - new_value, - description, - }) -} - -// Helper to create change commands for Vec -fn make_vec_string_change( - target: &T, - path: WritableOptionalKeyPath, P>, - read_path: OptionalKeyPath, R>, - new_value: Vec, - description: String, -) -> Box> -where - P: for<'r> Fn(&'r mut T) -> Option<&'r mut Vec> + 'static, - R: for<'r> Fn(&'r T) -> Option<&'r Vec> + 'static, -{ - let old_value = read_path.get(target).map(|v| v.clone()).unwrap_or_default(); - let path_rc = Rc::new(path); - let path_clone = path_rc.clone(); - let path_box: Box Option<&mut Vec>> = - Box::new(move |t: &mut T| path_clone.get_mut(t)); - Box::new(ChangeCommand { - path: path_box, - old_value, - new_value, - description, - }) -} - -fn main() { - println!("=== Undo/Redo System Demo ===\n"); - - // Create initial document - let mut doc = Document { - title: "My Document".to_string(), - content: "Hello, World!".to_string(), - metadata: DocumentMetadata { - author: "Akash".to_string(), - tags: vec!["draft".to_string()], - revision: 1, - }, - }; - - println!("Initial document:"); - println!("{:#?}\n", doc); - - // Create undo stack - let mut undo_stack = UndoStack::new(); - - // Change 1: Update title - println!("--- Change 1: Update title ---"); - let cmd = make_string_change( - &doc, - Document::title_w().to_optional(), - Document::title_r().to_optional(), - "Updated Document".to_string(), - "Change title to 'Updated Document'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Title: {}", doc.title); - - // Change 2: Update content - println!("\n--- Change 2: Update content ---"); - let cmd = make_string_change( - &doc, - Document::content_w().to_optional(), - Document::content_r().to_optional(), - "Hello, Rust!".to_string(), - "Change content to 'Hello, Rust!'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Content: {}", doc.content); - - // Change 3: Update nested author field - println!("\n--- Change 3: Update author (nested field) ---"); - let cmd = make_string_change( - &doc, - Document::metadata_w() - .to_optional() - .then(DocumentMetadata::author_w().to_optional()), - Document::metadata_r() - .to_optional() - .then(DocumentMetadata::author_r().to_optional()), - "Bob".to_string(), - "Change author to 'Bob'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Author: {}", doc.metadata.author); - - // Change 4: Update revision number - println!("\n--- Change 4: Update revision ---"); - let cmd = make_u32_change( - &doc, - Document::metadata_w() - .to_optional() - .then(DocumentMetadata::revision_w().to_optional()), - Document::metadata_r() - .to_optional() - .then(DocumentMetadata::revision_r().to_optional()), - 2, - "Increment revision to 2".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Revision: {}", doc.metadata.revision); - - // Change 5: Update tags - println!("\n--- Change 5: Update tags ---"); - let cmd = make_vec_string_change( - &doc, - Document::metadata_w() - .to_optional() - .then(DocumentMetadata::tags_w().to_optional()), - Document::metadata_r() - .to_optional() - .then(DocumentMetadata::tags_r().to_optional()), - vec!["draft".to_string(), "reviewed".to_string()], - "Add 'reviewed' tag".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Tags: {:?}", doc.metadata.tags); - - // Display current state - println!("\n=== Current State (After all changes) ==="); - println!("{:#?}", doc); - - // Display history - println!("\n=== Command History ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Perform undo operations - println!("\n=== Performing Undo Operations ==="); - - // Undo 1 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Tags: {:?}", doc.metadata.tags); - } - - // Undo 2 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("\n✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Revision: {}", doc.metadata.revision); - } - - // Undo 3 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("\n✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Author: {}", doc.metadata.author); - } - - println!("\n=== State After 3 Undos ==="); - println!("{:#?}", doc); - - // Display updated history - println!("\n=== Updated Command History ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Perform redo operations - println!("\n=== Performing Redo Operations ==="); - - // Redo 1 - if undo_stack.can_redo() { - match undo_stack.redo(&mut doc) { - Ok(desc) => println!("✓ Redone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Author: {}", doc.metadata.author); - } - - // Redo 2 - if undo_stack.can_redo() { - match undo_stack.redo(&mut doc) { - Ok(desc) => println!("\n✓ Redone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Revision: {}", doc.metadata.revision); - } - - println!("\n=== State After 2 Redos ==="); - println!("{:#?}", doc); - - // Make a new change (should clear redo history) - println!("\n=== Making New Change (clears redo history) ==="); - let cmd = make_string_change( - &doc, - Document::content_w().to_optional(), - Document::content_r().to_optional(), - "Hello, KeyPaths!".to_string(), - "Change content to 'Hello, KeyPaths!'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Content: {}", doc.content); - - println!("\n=== Command History (redo history cleared) ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Demonstrate full undo to beginning - println!("\n=== Undoing All Changes ==="); - let mut undo_count = 0; - while undo_stack.can_undo() { - if let Ok(desc) = undo_stack.undo(&mut doc) { - undo_count += 1; - println!("{}. Undone: {}", undo_count, desc); - } - } - - println!("\n=== State After Undoing Everything ==="); - println!("{:#?}", doc); - - // Verify we're back to the original state - println!("\n=== Verification ==="); - println!("Title matches original: {}", doc.title == "My Document"); - println!( - "Content matches original: {}", - doc.content == "Hello, World!" - ); - println!( - "Author matches original: {}", - doc.metadata.author == "Akash" - ); - println!("Revision matches original: {}", doc.metadata.revision == 1); - println!( - "Tags match original: {}", - doc.metadata.tags == vec!["draft".to_string()] - ); - - // Test redo all - println!("\n=== Redoing All Changes ==="); - let mut redo_count = 0; - while undo_stack.can_redo() { - if let Ok(desc) = undo_stack.redo(&mut doc) { - redo_count += 1; - println!("{}. Redone: {}", redo_count, desc); - } - } - - println!("\n=== Final State (After Redo All) ==="); - println!("{:#?}", doc); - - println!("\n=== Summary ==="); - println!("Total commands in history: {}", undo_stack.commands.len()); - println!("Can undo: {}", undo_stack.can_undo()); - println!("Can redo: {}", undo_stack.can_redo()); - - println!("\n✓ Undo/Redo demo complete!"); -} diff --git a/examples/undo_redo_static_dispatch.rs b/examples/undo_redo_static_dispatch.rs deleted file mode 100644 index 83c96f6..0000000 --- a/examples/undo_redo_static_dispatch.rs +++ /dev/null @@ -1,666 +0,0 @@ -// Demonstrates implementing undo/redo functionality using keypaths with static dispatch -// This example shows how to: -// 1. Track changes to deeply nested data structures using static dispatch -// 2. Implement command pattern for undo/redo without dynamic dispatch overhead -// 3. Handle multiple field types in undo/redo using enum variants -// 4. Support redo after undo operations -// 5. Display history of changes -// cargo run --example undo_redo_static_dispatch - -// ============================================================================ -// DATA STRUCTURES AND ALGORITHMS USED -// ============================================================================ -// -// ## Data Structures: -// -// 1. **Command Enum** (`Command`) -// - Purpose: Type-safe representation of different command types -// - Variants: One for each field type (String, u32, Vec) -// - Storage: Each variant stores: -// * Keypath directly (no Box, no closures) - stored as function pointer -// * Old value (cloned) - O(n) where n is value size -// * New value (cloned) - O(n) where n is value size -// * Description string - O(m) where m is string length -// - Memory: O(n + m) per command where n is value size, m is description length -// - Advantages: Zero-cost abstraction, compile-time type checking, no vtable overhead -// - Implementation: Uses macro to generate concrete command types for each keypath -// -// 2. **UndoStack** (`UndoStack`) -// - Purpose: Manages undo/redo history -// - Storage: Vec> - O(k) space where k is number of commands -// - Current pointer: usize - O(1) space -// - Memory: O(k * (n + m)) where k is command count, n is average value size, m is description length -// - Operations: -// * execute: O(1) amortized (Vec::push), O(k) worst case if redo history needs truncation -// * undo: O(1) - decrement pointer and apply command -// * redo: O(1) - increment pointer and apply command -// * history: O(k) - iterate through all commands -// -// 3. **KeyPaths** (from rust-keypaths crate) -// - Purpose: Zero-cost abstractions for field access -// - Storage: Function pointer/closure - O(1) size -// - Access: O(1) - direct field access, compiler optimizes to inline -// - Composition: O(1) - compile-time composition, no runtime overhead -// -// ## Algorithms: -// -// 1. **Command Pattern** -// - Pattern: Encapsulate operations as objects -// - Complexity: O(1) per command execution/undo -// - Space: O(n) per command (stores old/new values) -// -// 2. **Undo/Redo Stack Algorithm** -// - Algorithm: Linear history with current position pointer -// - Undo: Move pointer backward, apply inverse operation -// - Redo: Move pointer forward, reapply operation -// - New command: Truncate future history, append new command -// - Complexity: -// * Undo: O(1) - pointer decrement + value swap -// * Redo: O(1) - pointer increment + value swap -// * Execute: O(1) amortized, O(k) worst case (truncation) -// - Space: O(k) where k is maximum history length -// -// 3. **KeyPath Composition** -// - Algorithm: Compile-time function composition -// - Complexity: O(1) - no runtime overhead, compiler inlines -// - Depth: Supports arbitrary nesting depth -// -// ## Complexity Analysis: -// -// ### Time Complexity: -// - Command creation: O(n) where n is value size (cloning) -// - Command execution: O(1) - direct field access via keypath -// - Command undo: O(1) - direct field access via keypath -// - UndoStack::execute: O(1) amortized, O(k) worst case (when truncating redo history) -// - UndoStack::undo: O(1) -// - UndoStack::redo: O(1) -// - UndoStack::history: O(k) where k is command count -// -// ### Space Complexity: -// - Per command: O(n + m) where n is value size, m is description length -// - UndoStack: O(k * (n + m)) where k is command count -// - Keypaths: O(1) - function pointers/zero-sized types -// -// ## Alternative Approaches: -// -// 1. **Dynamic Dispatch (Box)** -// - Pros: Single type, easier to extend -// - Cons: Runtime overhead (vtable lookup), heap allocation, no compiler optimizations -// - Use when: Need runtime polymorphism, unknown types at compile time -// -// 2. **Trait Objects with Generics** -// - Pros: Type safety, some optimization -// - Cons: Still requires Box for storage, less flexible -// - Use when: Need trait bounds but can accept some overhead -// -// 3. **Macro-Generated Commands** -// - Pros: Zero overhead, type-safe, no Box needed -// - Cons: Code generation, less flexible, requires macro for each keypath -// - Use when: Fixed set of types, maximum performance needed (THIS APPROACH) -// -// 4. **Copy-on-Write (COW)** -// - Pros: Efficient for large values, shared state -// - Cons: More complex, overhead for small values -// - Use when: Large values, many undo/redo operations -// -// 5. **Delta/Patches** -// - Pros: Space efficient, only store changes -// - Cons: More complex, slower apply/revert -// - Use when: Large data structures, many small changes -// -// 6. **Memento Pattern** -// - Pros: Simple, full state snapshots -// - Cons: High memory usage, slow for large states -// - Use when: Small states, simple requirements -// -// 7. **Event Sourcing** -// - Pros: Complete audit trail, time travel -// - Cons: High memory usage, complex replay logic -// - Use when: Need full history, audit requirements -// -// ============================================================================ - -use keypaths_proc::Kp; - -#[derive(Debug, Clone, Kp)] -#[All] -struct Document { - title: String, - content: String, - metadata: DocumentMetadata, -} - -#[derive(Debug, Clone, Kp)] -#[All] -struct DocumentMetadata { - author: String, - tags: Vec, - revision: u32, -} - -// ============================================================================ -// STATIC DISPATCH COMMAND ENUM -// ============================================================================ -// Uses enum variants for each field type, storing keypaths directly -// No Box, no trait objects, no dynamic dispatch - pure static dispatch -// Each variant stores the keypath's behavior via a function pointer -// The keypaths are converted to function pointers at creation time - -enum Command { - // String field command - // Stores: function pointer to keypath's get_mut, old value, new value, description - // Size: O(1) for function pointer + O(n) for values where n is string length - String { - get_mut: fn(&mut T) -> Option<&mut String>, - old_value: String, - new_value: String, - description: String, - }, - // u32 field command - // Stores: function pointer to keypath's get_mut, old value, new value, description - // Size: O(1) for function pointer + O(1) for u32 values - U32 { - get_mut: fn(&mut T) -> Option<&mut u32>, - old_value: u32, - new_value: u32, - description: String, - }, - // Vec field command - // Stores: function pointer to keypath's get_mut, old value, new value, description - // Size: O(1) for function pointer + O(n) for values where n is total string length - VecString { - get_mut: fn(&mut T) -> Option<&mut Vec>, - old_value: Vec, - new_value: Vec, - description: String, - }, -} - -impl Command { - // Execute the command - applies new_value via keypath function pointer - // Time: O(1) - direct field access, compiler optimizes function pointer call to inline - // Space: O(1) - no allocations - fn execute(&self, target: &mut T) { - match self { - Command::String { - get_mut, new_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = new_value.clone(); - } - } - Command::U32 { - get_mut, new_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = *new_value; - } - } - Command::VecString { - get_mut, new_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = new_value.clone(); - } - } - } - } - - // Undo the command - restores old_value via keypath function pointer - // Time: O(1) - direct field access, compiler optimizes function pointer call to inline - // Space: O(1) - no allocations - fn undo(&self, target: &mut T) { - match self { - Command::String { - get_mut, old_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = old_value.clone(); - } - } - Command::U32 { - get_mut, old_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = *old_value; - } - } - Command::VecString { - get_mut, old_value, .. - } => { - if let Some(field) = get_mut(target) { - *field = old_value.clone(); - } - } - } - } - - // Get command description - // Time: O(1) - direct field access - fn description(&self) -> &str { - match self { - Command::String { description, .. } => description, - Command::U32 { description, .. } => description, - Command::VecString { description, .. } => description, - } - } -} - -// ============================================================================ -// UNDO/REDO STACK WITH STATIC DISPATCH -// ============================================================================ -// Uses Vec> for storage - all commands are the same enum type -// No Box, no trait objects - pure static dispatch -// Current pointer tracks position in history for undo/redo - -struct UndoStack { - // Storage: Vec of commands - // Space: O(k * (n + m)) where k is command count, n is average value size, m is description length - commands: Vec>, - // Current position pointer - points to next position to add command - // O(1) space - current: usize, -} - -impl UndoStack { - // Create new empty undo stack - // Time: O(1) - // Space: O(1) - empty Vec has minimal overhead - fn new() -> Self { - Self { - commands: Vec::new(), - current: 0, - } - } - - // Execute a new command and add it to the stack - // Algorithm: - // 1. Execute command immediately - // 2. If not at end, truncate redo history (branch prediction helps here) - // 3. Push command to stack - // 4. Increment current pointer - // Time: O(1) amortized (Vec::push), O(k) worst case if truncation needed - // Space: O(1) amortized, O(k) worst case if reallocation needed - fn execute(&mut self, target: &mut T, command: Command) { - // Execute the command - command.execute(target); - - // If we're not at the end, truncate the redo history - // This is O(k) where k is number of commands to remove - // But amortized over many operations, it's O(1) - if self.current < self.commands.len() { - self.commands.truncate(self.current); - } - - // Add the command to the stack - // Vec::push is O(1) amortized - self.commands.push(command); - self.current += 1; - } - - // Undo the last command - // Algorithm: - // 1. Check if undo is possible (current > 0) - // 2. Decrement current pointer - // 3. Get command at current position - // 4. Apply undo operation - // Time: O(1) - pointer decrement + command undo - // Space: O(1) - no allocations - fn undo(&mut self, target: &mut T) -> Result { - if self.current == 0 { - return Err("Nothing to undo".into()); - } - - self.current -= 1; - let command = &self.commands[self.current]; - let desc = command.description().to_string(); - command.undo(target); - Ok(desc) - } - - // Redo the last undone command - // Algorithm: - // 1. Check if redo is possible (current < commands.len()) - // 2. Get command at current position - // 3. Apply execute operation - // 4. Increment current pointer - // Time: O(1) - pointer increment + command execute - // Space: O(1) - no allocations - fn redo(&mut self, target: &mut T) -> Result { - if self.current >= self.commands.len() { - return Err("Nothing to redo".into()); - } - - let command = &self.commands[self.current]; - let desc = command.description().to_string(); - command.execute(target); - self.current += 1; - Ok(desc) - } - - // Check if undo is available - // Time: O(1) - simple comparison - fn can_undo(&self) -> bool { - self.current > 0 - } - - // Check if redo is available - // Time: O(1) - simple comparison - fn can_redo(&self) -> bool { - self.current < self.commands.len() - } - - // Get the history of commands with execution markers - // Algorithm: Iterate through all commands, mark executed ones - // Time: O(k) where k is command count - // Space: O(k) for the returned Vec - fn history(&self) -> Vec { - self.commands - .iter() - .enumerate() - .map(|(i, cmd)| { - let marker = if i < self.current { "✓" } else { " " }; - format!("{} {}", marker, cmd.description()) - }) - .collect() - } -} - -// ============================================================================ -// HELPER FUNCTIONS FOR CREATING COMMANDS -// ============================================================================ -// These functions create commands by capturing current value and converting -// keypaths to function pointers. Since we can't convert closures to function -// pointers directly, we create wrapper functions for each specific keypath. -// In practice, you would use a macro to generate these functions. - -// Helper: Create command creation functions for each specific keypath -// Note: We use direct field access in function pointers because Rust doesn't -// allow storing closures (from keypaths) in enums without Box. The keypaths -// are still used conceptually - we're accessing the same fields that the -// keypaths would access, just using function pointers instead of closures. -// In a production system, you would use a macro to generate these functions -// automatically from the keypath definitions. - -// Create a String change command using title keypath -fn make_string_change_title( - target: &Document, - new_value: String, - description: String, -) -> Command { - let old_value = target.title.clone(); - Command::String { - get_mut: |t: &mut Document| Some(&mut t.title), - old_value, - new_value, - description, - } -} - -// Create a String change command using content keypath -fn make_string_change_content( - target: &Document, - new_value: String, - description: String, -) -> Command { - let old_value = target.content.clone(); - Command::String { - get_mut: |t: &mut Document| Some(&mut t.content), - old_value, - new_value, - description, - } -} - -// Create a String change command using nested author keypath -fn make_string_change_author( - target: &Document, - new_value: String, - description: String, -) -> Command { - let old_value = target.metadata.author.clone(); - Command::String { - get_mut: |t: &mut Document| Some(&mut t.metadata.author), - old_value, - new_value, - description, - } -} - -// Create a u32 change command using revision keypath -fn make_u32_change_revision( - target: &Document, - new_value: u32, - description: String, -) -> Command { - let old_value = target.metadata.revision; - Command::U32 { - get_mut: |t: &mut Document| Some(&mut t.metadata.revision), - old_value, - new_value, - description, - } -} - -// Create a Vec change command using tags keypath -fn make_vec_string_change_tags( - target: &Document, - new_value: Vec, - description: String, -) -> Command { - let old_value = target.metadata.tags.clone(); - Command::VecString { - get_mut: |t: &mut Document| Some(&mut t.metadata.tags), - old_value, - new_value, - description, - } -} - -fn main() { - println!("=== Undo/Redo System Demo (Static Dispatch) ===\n"); - - // Create initial document - let mut doc = Document { - title: "My Document".to_string(), - content: "Hello, World!".to_string(), - metadata: DocumentMetadata { - author: "Akash".to_string(), - tags: vec!["draft".to_string()], - revision: 1, - }, - }; - - println!("Initial document:"); - println!("{:#?}\n", doc); - - // Create undo stack - let mut undo_stack = UndoStack::new(); - - // Change 1: Update title - println!("--- Change 1: Update title ---"); - let cmd = make_string_change_title( - &doc, - "Updated Document".to_string(), - "Change title to 'Updated Document'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Title: {}", doc.title); - - // Change 2: Update content - println!("\n--- Change 2: Update content ---"); - let cmd = make_string_change_content( - &doc, - "Hello, Rust!".to_string(), - "Change content to 'Hello, Rust!'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Content: {}", doc.content); - - // Change 3: Update nested author field - println!("\n--- Change 3: Update author (nested field) ---"); - let cmd = make_string_change_author( - &doc, - "Bob".to_string(), - "Change author to 'Bob'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Author: {}", doc.metadata.author); - - // Change 4: Update revision number - println!("\n--- Change 4: Update revision ---"); - let cmd = make_u32_change_revision(&doc, 2, "Increment revision to 2".to_string()); - undo_stack.execute(&mut doc, cmd); - println!("Revision: {}", doc.metadata.revision); - - // Change 5: Update tags - println!("\n--- Change 5: Update tags ---"); - let cmd = make_vec_string_change_tags( - &doc, - vec!["draft".to_string(), "reviewed".to_string()], - "Add 'reviewed' tag".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Tags: {:?}", doc.metadata.tags); - - // Display current state - println!("\n=== Current State (After all changes) ==="); - println!("{:#?}", doc); - - // Display history - println!("\n=== Command History ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Perform undo operations - println!("\n=== Performing Undo Operations ==="); - - // Undo 1 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Tags: {:?}", doc.metadata.tags); - } - - // Undo 2 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("\n✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Revision: {}", doc.metadata.revision); - } - - // Undo 3 - if undo_stack.can_undo() { - match undo_stack.undo(&mut doc) { - Ok(desc) => println!("\n✓ Undone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Author: {}", doc.metadata.author); - } - - println!("\n=== State After 3 Undos ==="); - println!("{:#?}", doc); - - // Display updated history - println!("\n=== Updated Command History ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Perform redo operations - println!("\n=== Performing Redo Operations ==="); - - // Redo 1 - if undo_stack.can_redo() { - match undo_stack.redo(&mut doc) { - Ok(desc) => println!("✓ Redone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Author: {}", doc.metadata.author); - } - - // Redo 2 - if undo_stack.can_redo() { - match undo_stack.redo(&mut doc) { - Ok(desc) => println!("\n✓ Redone: {}", desc), - Err(e) => println!("✗ {}", e), - } - println!("Revision: {}", doc.metadata.revision); - } - - println!("\n=== State After 2 Redos ==="); - println!("{:#?}", doc); - - // Make a new change (should clear redo history) - println!("\n=== Making New Change (clears redo history) ==="); - let cmd = make_string_change_content( - &doc, - "Hello, KeyPaths!".to_string(), - "Change content to 'Hello, KeyPaths!'".to_string(), - ); - undo_stack.execute(&mut doc, cmd); - println!("Content: {}", doc.content); - - println!("\n=== Command History (redo history cleared) ==="); - for (i, entry) in undo_stack.history().iter().enumerate() { - println!("{}. {}", i + 1, entry); - } - - // Demonstrate full undo to beginning - println!("\n=== Undoing All Changes ==="); - let mut undo_count = 0; - while undo_stack.can_undo() { - if let Ok(desc) = undo_stack.undo(&mut doc) { - undo_count += 1; - println!("{}. Undone: {}", undo_count, desc); - } - } - - println!("\n=== State After Undoing Everything ==="); - println!("{:#?}", doc); - - // Verify we're back to the original state - println!("\n=== Verification ==="); - println!("Title matches original: {}", doc.title == "My Document"); - println!( - "Content matches original: {}", - doc.content == "Hello, World!" - ); - println!( - "Author matches original: {}", - doc.metadata.author == "Akash" - ); - println!("Revision matches original: {}", doc.metadata.revision == 1); - println!( - "Tags match original: {}", - doc.metadata.tags == vec!["draft".to_string()] - ); - - // Test redo all - println!("\n=== Redoing All Changes ==="); - let mut redo_count = 0; - while undo_stack.can_redo() { - if let Ok(desc) = undo_stack.redo(&mut doc) { - redo_count += 1; - println!("{}. Redone: {}", redo_count, desc); - } - } - - println!("\n=== Final State (After Redo All) ==="); - println!("{:#?}", doc); - - println!("\n=== Summary ==="); - println!("Total commands in history: {}", undo_stack.commands.len()); - println!("Can undo: {}", undo_stack.can_undo()); - println!("Can redo: {}", undo_stack.can_redo()); - - println!("\n✓ Undo/Redo demo complete (Static Dispatch)!"); - println!("\nNote: This implementation uses function pointers to store keypaths"); - println!("without Box. For dynamic keypaths, you would use a macro to generate"); - println!("concrete command creation functions for each keypath combination."); -} diff --git a/examples/universal_lock_adaptation_example.rs b/examples/universal_lock_adaptation_example.rs deleted file mode 100644 index dc60104..0000000 --- a/examples/universal_lock_adaptation_example.rs +++ /dev/null @@ -1,210 +0,0 @@ -use keypaths_proc::Kp; -use parking_lot::{Mutex, RwLock}; -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use std::sync::Arc; - -#[derive(Kp, Clone)] -#[All] -struct User { - name: String, - age: u32, - email: Option, -} - -#[derive(Kp, Clone)] -#[All] -struct Profile { - user: User, - bio: String, -} - -fn main() { - println!("🔒 Universal Lock Adaptation Example"); - println!("===================================="); - - // Create data wrapped in parking_lot synchronization primitives - let user = User { - name: "Akash".to_string(), - age: 30, - email: Some("akash@example.com".to_string()), - }; - - let profile = Profile { - user: user.clone(), - bio: "Software engineer with passion for Rust".to_string(), - }; - - let parking_mutex_user = Arc::new(Mutex::new(user)); - let parking_rwlock_profile = Arc::new(RwLock::new(profile)); - - println!("\n📝 Working with parking_lot::Mutex"); - println!("----------------------------------"); - - // Method 1: Direct access with parking_lot::Mutex - let name_keypath = User::name_r(); - let name_keypath_w = User::name_w(); - - // Access name through parking_lot::Mutex - { - let guard = parking_mutex_user.lock(); - let name = name_keypath.get(&*guard); - println!("✅ Name from parking_lot::Mutex: {}", name); - } - - // Modify name through parking_lot::Mutex - { - let mut guard = parking_mutex_user.lock(); - let name = name_keypath_w.get_mut(&mut *guard); - *name = "Akash Updated".to_string(); - println!("✅ Updated name in parking_lot::Mutex: {}", name); - } - - println!("\n📝 Working with parking_lot::RwLock"); - println!("-----------------------------------"); - - // Method 2: Direct access with parking_lot::RwLock - let bio_keypath = Profile::bio_r(); - let bio_keypath_w = Profile::bio_w(); - let user_name_keypath = Profile::user_r() - .to_optional() - .then(User::name_r().to_optional()); - - // Read access through parking_lot::RwLock - { - let guard = parking_rwlock_profile.read(); - let bio = bio_keypath.get(&*guard); - println!("✅ Bio from parking_lot::RwLock: {}", bio); - - if let Some(name) = user_name_keypath.get(&*guard) { - println!("✅ Nested name from parking_lot::RwLock: {}", name); - } - } - - // Write access through parking_lot::RwLock - { - let mut guard = parking_rwlock_profile.write(); - let bio = bio_keypath_w.get_mut(&mut *guard); - *bio = "Senior software engineer with passion for Rust and systems programming".to_string(); - println!("✅ Updated bio in parking_lot::RwLock: {}", bio); - } - - println!("\n🔧 Creating Universal Lock Adapters"); - println!("-----------------------------------"); - - // Method 3: Create adapter functions for universal locks - let name_keypath = User::name_r(); - - // Adapter for parking_lot::Mutex - fn parking_mutex_adapter( - keypath: KeyPath Fn(&'r User) -> &'r String>, - mutex: &Mutex, - f: F, - ) where - F: FnOnce(&String), - { - let guard = mutex.lock(); - let value = keypath.get(&*guard); - f(value); - } - - // Adapter for parking_lot::RwLock - fn parking_rwlock_adapter( - keypath: KeyPath Fn(&'r Profile) -> &'r String>, - rwlock: &RwLock, - f: F, - ) where - F: FnOnce(&String), - { - let guard = rwlock.read(); - let value = keypath.get(&*guard); - f(value); - } - - // Use the adapters - parking_mutex_adapter(name_keypath, &parking_mutex_user, |name| { - println!("✅ Adapter - Name from parking_lot::Mutex: {}", name); - }); - - parking_rwlock_adapter(bio_keypath, &parking_rwlock_profile, |bio| { - println!("✅ Adapter - Bio from parking_lot::RwLock: {}", bio); - }); - - println!("\n🔄 Simple Universal Lock Adapter"); - println!("--------------------------------"); - - // Method 4: Simple adapter that works with parking_lot locks - fn with_parking_mutex( - keypath: KeyPath Fn(&'r T) -> &'r V>, - mutex: &Mutex, - f: F, - ) -> R - where - F: FnOnce(&V) -> R, - { - let guard = mutex.lock(); - f(keypath.get(&*guard)) - } - - fn with_parking_rwlock( - keypath: KeyPath Fn(&'r T) -> &'r V>, - rwlock: &RwLock, - f: F, - ) -> R - where - F: FnOnce(&V) -> R, - { - let guard = rwlock.read(); - f(keypath.get(&*guard)) - } - - // Use the simple adapters - { - let name_keypath = User::name_r(); - let name = with_parking_mutex(name_keypath, &parking_mutex_user, |name: &String| { - name.clone() - }); - println!("✅ Simple adapter - Name from parking_lot::Mutex: {}", name); - } - - { - let bio_keypath = Profile::bio_r(); - let bio = with_parking_rwlock(bio_keypath, &parking_rwlock_profile, |bio: &String| { - bio.clone() - }); - println!("✅ Simple adapter - Bio from parking_lot::RwLock: {}", bio); - } - - println!("\n🎯 Advanced: Working with Nested KeyPaths"); - println!("----------------------------------------"); - - // Demonstrate composition with nested keypaths using direct access - let nested_name_keypath = Profile::user_r() - .to_optional() - .then(User::name_r().to_optional()); - { - let guard = parking_rwlock_profile.read(); - if let Some(name) = nested_name_keypath.get(&*guard) { - println!("✅ Nested name from parking_lot::RwLock: {}", name); - } - } - - // Demonstrate working with Option fields - let email_keypath = User::email_fr(); - { - let guard = parking_mutex_user.lock(); - if let Some(email) = email_keypath.get(&*guard) { - println!("✅ Email from parking_lot::Mutex: {}", email); - } else { - println!("✅ No email in user"); - } - } - - println!("\n💡 Key Takeaways:"); - println!("=================="); - println!("1. Direct access: Use lock guards with keypath.get()/get_mut()"); - println!("2. Adapter functions: Create simple functions that handle locking"); - println!("3. Generic adapters: Use traits to work with multiple lock types"); - println!("4. Composable adapters: Create reusable adapter structs"); - println!("5. parking_lot provides better performance than std::sync primitives"); - println!("6. Universal adapters work with any lock that implements the trait"); -} diff --git a/examples/vec.rs b/examples/vec.rs deleted file mode 100644 index 3da730b..0000000 --- a/examples/vec.rs +++ /dev/null @@ -1,123 +0,0 @@ -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -// use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -use keypaths_proc::{Casepaths, Kp}; - -#[derive(Debug, Kp)] -#[All] -struct SomeComplexStruct { - scsf: Vec, -} - -// impl SomeComplexStruct { -// fn scsf_fr() -> KeyPaths { -// OptionalKeyPath::new( -// |root: & SomeComplexStruct| -// { -// root.scsf.first() -// } -// ) -// } - -// fn scsf_fr_at(index: &'static usize) -> KeyPaths { -// OptionalKeyPath::new( -// |root: & SomeComplexStruct| -// { -// root.scsf.get(*index) -// } -// ) -// } - -// fn scsf_fw() -> KeyPaths { -// WritableOptionalKeyPath::new( -// |root: &mut SomeComplexStruct| -// { -// root.scsf.first_mut() -// } -// ) -// } - -// fn scsf_fw_at(index: usize) -> KeyPaths { -// WritableOptionalKeyPath::new( -// move |root: &mut SomeComplexStruct| -// { -// root.scsf.get_mut(index) -// } -// ) -// } - -// } -impl SomeComplexStruct { - fn new() -> Self { - Self { - scsf: vec![ - SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }, - SomeOtherStruct { - sosf: OneMoreStruct { - omsf: String::from("no value for now"), - omse: SomeEnum::B(DarkStruct { - dsf: String::from("dark field"), - }), - }, - }, - ], - } - } -} - -#[derive(Debug, Kp)] -#[All] -struct SomeOtherStruct { - sosf: OneMoreStruct, -} - -#[derive(Debug, Casepaths)] -#[All] -enum SomeEnum { - A(Vec), - B(DarkStruct), -} - -#[derive(Debug, Kp)] -#[All] -struct OneMoreStruct { - omsf: String, - omse: SomeEnum, -} - -#[derive(Debug, Kp)] -#[All] -struct DarkStruct { - dsf: String, -} - -fn main() { - // let x = ; - let op = SomeComplexStruct::scsf_fw_at(1) - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_w()) - .then(DarkStruct::dsf_fw()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get_mut(&mut instance) { - *omsf = String::from("we can change the field with the other way unlocked by keypaths"); - } - println!("instance = {:?}", instance); - - let op = SomeComplexStruct::scsf_fw() - .then(SomeOtherStruct::sosf_fw()) - .then(OneMoreStruct::omse_fw()) - .then(SomeEnum::b_w()) - .then(DarkStruct::dsf_fw()); - let mut instance = SomeComplexStruct::new(); - if let Some(omsf) = op.get_mut(&mut instance) { - *omsf = String::from("we can change the field with the other way unlocked by keypaths"); - } - println!("instance = {:?}", instance); -} diff --git a/examples/with_container_trait_example.rs b/examples/with_container_trait_example.rs deleted file mode 100644 index 7814bf3..0000000 --- a/examples/with_container_trait_example.rs +++ /dev/null @@ -1,194 +0,0 @@ -// Example demonstrating the WithContainer trait usage -// Run with: cargo run --example with_container_trait_example - -use rust_keypaths::{KeyPath, OptionalKeyPath, WritableKeyPath, WritableOptionalKeyPath}; -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!("=== WithContainer Trait 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 name_path_w = WritableKeyPath::new(|u: &mut User| &mut u.name); - - // ===== Example 1: Trait Usage with Arc ===== - println!("--- Example 1: Trait Usage with Arc ---"); - - let arc_user = Arc::new(user.clone()); - - // Using the method directly (Arc doesn't support direct mutable access without interior mutability) - let name = name_path.get(&*arc_user); - println!(" Name from Arc: {}", name); - - // ===== Example 2: Trait Usage with Box ===== - println!("--- Example 2: Trait Usage with Box ---"); - - let mut boxed_user = Box::new(user.clone()); - - // Read directly from Box (Box implements Deref) - let name = name_path.get(&*boxed_user); - println!(" Name from Box: {}", name); - - // Write directly to Box - { - let name = name_path_w.get_mut(&mut *boxed_user); - *name = "Akash Boxed".to_string(); - println!(" Updated name in Box: {}", name); - } - - // ===== Example 3: Trait Usage with Rc ===== - println!("--- Example 3: Trait Usage with Rc ---"); - - let rc_user = Rc::new(user.clone()); - - // Read directly from Rc (Rc implements Deref) - let name = name_path.get(&*rc_user); - println!(" Name from Rc: {}", name); - - // ===== Example 4: Trait Usage with Result ===== - println!("--- Example 4: Trait Usage with Result ---"); - - let mut result_user: Result = Ok(user.clone()); - - // Read via EnumKeyPath::for_ok() - use rust_keypaths::EnumKeyPath; - let name_path_clone = KeyPath::new(|u: &User| &u.name); - let name_path_result = - EnumKeyPath::for_ok::().then(name_path_clone.to_optional()); - if let Some(name) = name_path_result.get(&result_user) { - println!(" Name from Result: {}", name); - } - - // Write via EnumKeyPath::for_ok() for writable - need to use WritableOptionalKeyPath - // For writable, we need to manually create the keypath - let name_path_w_result = WritableOptionalKeyPath::new(|result: &mut Result| { - result.as_mut().ok().map(|u| &mut u.name) - }); - if let Some(name) = name_path_w_result.get_mut(&mut result_user) { - *name = "Akash Result".to_string(); - println!(" Updated name in Result: {}", name); - } - - // ===== Example 5: Trait Usage with Option ===== - println!("--- Example 5: Trait Usage with Option ---"); - - let mut option_user: Option = Some(user.clone()); - - // Read via OptionalKeyPath - need to chain through Option first - let name_path_clone2 = KeyPath::new(|u: &User| &u.name); - let option_path = EnumKeyPath::for_some::(); - let name_path_through_option = option_path.then(name_path_clone2.to_optional()); - if let Some(name) = name_path_through_option.get(&option_user) { - println!(" Name from Option: {}", name); - } - - // Write via WritableOptionalKeyPath - need to chain through Option first - let name_path_w_clone = WritableKeyPath::new(|u: &mut User| &mut u.name); - let option_path_w = WritableOptionalKeyPath::new(|opt: &mut Option| opt.as_mut()); - let name_path_w_through_option = option_path_w.then(name_path_w_clone.to_optional()); - if let Some(name) = name_path_w_through_option.get_mut(&mut option_user) { - *name = "Akash Option".to_string(); - println!(" Updated name in Option: {}", name); - } - - // ===== Example 6: Trait Usage with RefCell ===== - println!("--- Example 6: Trait Usage with RefCell ---"); - - let refcell_user = RefCell::new(user.clone()); - - // Read via RefCell (RefCell provides interior mutability) - { - let user_ref = refcell_user.borrow(); - let name_path_clone = KeyPath::new(|u: &User| &u.name); - let name = name_path_clone.get(&*user_ref); - println!(" Name from RefCell: {}", name); - } - - // Write via RefCell - { - let mut user_ref = refcell_user.borrow_mut(); - let name_path_w_clone = WritableKeyPath::new(|u: &mut User| &mut u.name); - let name = name_path_w_clone.get_mut(&mut *user_ref); - *name = "Akash RefCell".to_string(); - println!(" Updated name in RefCell: {}", name); - } - - // ===== Example 7: Trait Usage with Mutex ===== - println!("--- Example 7: Trait Usage with Mutex ---"); - - let mutex_user = Mutex::new(user.clone()); - - // Read via with_mutex (OptionalKeyPath has this method) - // Note: with_mutex requires Clone, so we need to ensure the keypath is Clone - // For now, access Mutex directly - { - let guard = mutex_user.lock().unwrap(); - let name = name_path.get(&*guard); - println!(" Name from Mutex: {}", name); - } - - // Write via Mutex directly - let mut mutex_user_mut = Mutex::new(user.clone()); - { - let mut guard = mutex_user_mut.lock().unwrap(); - let name = name_path_w.get_mut(&mut *guard); - *name = "Akash Mutexed".to_string(); - println!(" Updated name in Mutex: {}", name); - } - - // ===== Example 8: Trait Usage with RwLock ===== - println!("--- Example 8: Trait Usage with RwLock ---"); - - let rwlock_user = RwLock::new(user.clone()); - - // Read via RwLock directly - { - let guard = rwlock_user.read().unwrap(); - let name = name_path.get(&*guard); - println!(" Name from RwLock: {}", name); - } - - // Write via RwLock directly - let mut rwlock_user_mut = RwLock::new(user.clone()); - let age_path_w = WritableKeyPath::new(|u: &mut User| &mut u.age); - { - let mut guard = rwlock_user_mut.write().unwrap(); - let age = age_path_w.get_mut(&mut *guard); - *age += 1; - println!(" Updated age in RwLock: {}", age); - } - - // ===== Example 9: Generic Function Using Methods ===== - println!("--- Example 9: Generic Function Using Methods ---"); - - println!(" Methods are available directly on keypath types"); - println!(" Use with_option(), with_mutex(), with_rwlock(), etc."); - - // ===== Example 10: Method Benefits ===== - println!("--- Example 10: Method Benefits ---"); - - println!(" ✅ Clean API: All with_* methods are available on keypath types"); - println!(" ✅ Extensibility: Easy to add new container types"); - println!(" ✅ Consistency: All methods follow the same pattern"); - println!(" ✅ Documentation: Methods are documented on each keypath type"); - println!(" ✅ Type Safety: Compile-time guarantees for container access"); - - println!("=== All Examples Completed Successfully! ==="); -} diff --git a/examples/writable_keypaths_new_containers_test.rs b/examples/writable_keypaths_new_containers_test.rs deleted file mode 100644 index fce3e6f..0000000 --- a/examples/writable_keypaths_new_containers_test.rs +++ /dev/null @@ -1,68 +0,0 @@ -use keypaths_proc::Keypaths; -use rust_keypaths::KeyPath; -use std::rc::Weak; -use std::sync::{Mutex, RwLock}; - -#[derive(Debug, Kp)] -struct ContainerTest { - // Error handling containers - result: Result, - result_int: Result, - - // Synchronization primitives - mutex_data: Mutex, - rwlock_data: RwLock, - - // Reference counting with weak references - weak_ref: Weak, - - // Basic types for comparison - name: String, - age: u32, -} - -fn main() { - println!("=== WritableKeypaths Macro New Container Types Test ==="); - - let mut container = ContainerTest { - result: Ok("Success!".to_string()), - result_int: Ok(42), - mutex_data: Mutex::new("Mutex content".to_string()), - rwlock_data: RwLock::new(100), - weak_ref: Weak::new(), - name: "Akash".to_string(), - age: 30, - }; - - // Test Result with WritableKeypaths - if let Some(result_ref) = ContainerTest::result_fr().get(&mut container) { - println!("✅ Result reference: {:?}", result_ref); - } - - // Test Mutex with WritableKeypaths - if let Some(mutex_ref) = ContainerTest::mutex_data_r() - .to_optional() - .get(&mut container) - { - println!("✅ Mutex reference: {:?}", mutex_ref); - } - - // Test RwLock with WritableKeypaths - // if let Some(rwlock_ref) = ContainerTest::rwlock_data_rwlock_fr_at(KeyPath::).get(&container) { - // println!("✅ RwLock reference: {:?}", rwlock_ref); - // } - - // Note: Weak doesn't have writable methods (it's immutable) - - // Test basic types - if let Some(name_ref) = ContainerTest::name_r().to_optional().get(&container) { - println!("✅ Name reference: {:?}", name_ref); - } - - if let Some(age_ref) = ContainerTest::age_r().to_optional().get(&mut container) { - println!("✅ Age reference: {:?}", age_ref); - } - - println!("\n=== WritableKeypaths Macro - All new container types supported! ==="); - println!("Note: Weak doesn't support writable access (it's immutable)"); -} diff --git a/examples/writable_keypaths_simple.rs b/examples/writable_keypaths_simple.rs deleted file mode 100644 index 11bf27e..0000000 --- a/examples/writable_keypaths_simple.rs +++ /dev/null @@ -1,72 +0,0 @@ -use keypaths_proc::WritableKeypaths; - -#[derive(Debug, WritableKeypaths)] -struct Person { - name: String, - age: u32, - email: Option, - hobbies: Vec, - scores: std::collections::HashMap, -} - -fn main() { - let mut person = Person { - name: "John Doe".to_string(), - age: 25, - email: Some("john@example.com".to_string()), - hobbies: vec!["reading".to_string(), "coding".to_string()], - scores: { - let mut map = std::collections::HashMap::new(); - map.insert("math".to_string(), 95); - map.insert("science".to_string(), 88); - map - }, - }; - - println!("=== Initial State ==="); - println!("Name: {}", person.name); - println!("Age: {}", person.age); - println!("Email: {:?}", person.email); - println!("Hobbies: {:?}", person.hobbies); - println!("Scores: {:?}", person.scores); - - // Basic writable keypaths - if let Some(name_ref) = Person::name_fw().get_mut(&mut person) { - *name_ref = "John Smith".to_string(); - println!("\nUpdated name to: {}", name_ref); - } - - if let Some(age_ref) = Person::age_fw().get_mut(&mut person) { - *age_ref = 26; - println!("Updated age to: {}", age_ref); - } - - // Failable writable keypaths - if let Some(email_ref) = Person::email_fw().get_mut(&mut person) { - *email_ref = "john.smith@example.com".to_string(); - println!("Updated email to: {}", email_ref); - } - - if let Some(hobby_ref) = Person::hobbies_fw().get_mut(&mut person) { - *hobby_ref = "gaming".to_string(); - println!("Updated first hobby to: {}", hobby_ref); - } - - if let Some(score_ref) = Person::scores_fw("math".to_string()).get_mut(&mut person) { - *score_ref = 98; - println!("Updated math score to: {}", score_ref); - } - - // Indexed access for Vec - if let Some(hobby_ref) = Person::hobbies_fw_at(1).get_mut(&mut person) { - *hobby_ref = "swimming".to_string(); - println!("Updated second hobby to: {}", hobby_ref); - } - - println!("\n=== Final State ==="); - println!("Name: {}", person.name); - println!("Age: {}", person.age); - println!("Email: {:?}", person.email); - println!("Hobbies: {:?}", person.hobbies); - println!("Scores: {:?}", person.scores); -} diff --git a/key-paths-derive/tests/comprehensive_test.rs b/key-paths-derive/tests/comprehensive_test.rs deleted file mode 100644 index 01151e4..0000000 --- a/key-paths-derive/tests/comprehensive_test.rs +++ /dev/null @@ -1,459 +0,0 @@ -use key_paths_derive::Kp; -use rust_key_paths::{KpTrait, KpType, LockKp}; -use std::borrow::Cow; -use std::collections::{BTreeMap, HashMap, VecDeque}; -use std::sync::Arc; -use std::sync::atomic::{AtomicI32, AtomicU64, Ordering}; - -// Test collections -#[derive(Kp)] -struct Collections { - items: Vec, - queue: VecDeque, -} - -// Test smart pointers -#[derive(Kp)] -struct SmartPointers { - boxed: Box, - rc: std::rc::Rc, - arc: std::sync::Arc, -} - -// Test locks -#[derive(Kp)] -struct WithLocks { - std_mutex: std::sync::Mutex, - std_rwlock: std::sync::RwLock, -} - -// Test tokio async locks (requires rust-key-paths tokio feature) -#[derive(Kp)] -struct WithTokioLocks { - data: Arc>>, -} - -// Test Option>> -#[derive(Kp)] -struct WithOptionTokioLocks { - data: Option>>, -} - -// Test Cow and Option -#[derive(Kp)] -struct WithCow { - cow_owned: Cow<'static, String>, - cow_borrowed: Cow<'static, String>, - opt_cow: Option>, -} - -// Test HashMap and BTreeMap _at(key) -#[derive(Kp)] -struct WithMaps { - users: HashMap, - cache: BTreeMap, -} - -// Test atomic types -#[derive(Kp)] -struct WithAtomics { - counter: AtomicI32, - flags: AtomicU64, -} - -// Test Option -#[derive(Kp)] -struct WithOptionAtomic { - opt_counter: Option, -} - -#[test] -fn test_identity_keypath() { - let collections = Collections { - items: vec![1, 2, 3], - queue: VecDeque::new(), - }; - - // Test identity keypath returns the struct itself - let identity_kp = Collections::identity(); - let result = identity_kp.get(&collections); - assert!(result.is_some()); - assert_eq!(result.unwrap().items.len(), 3); -} - -#[test] -fn test_identity_mutable() { - let mut collections = Collections { - items: vec![1, 2, 3], - queue: VecDeque::new(), - }; - - // Test identity keypath can mutate the struct - let identity_kp = Collections::identity(); - identity_kp.get_mut(&mut collections).map(|c| { - c.items.push(4); - }); - - assert_eq!(collections.items.len(), 4); -} - -#[test] -fn test_identity_typed() { - let smart = SmartPointers { - boxed: Box::new("test".to_string()), - rc: std::rc::Rc::new(42), - arc: std::sync::Arc::new("arc".to_string()), - }; - - // Test typed identity keypath - let identity_kp = SmartPointers::identity_typed::<&SmartPointers, &mut SmartPointers>(); - let result = identity_kp.get(&smart); - assert!(result.is_some()); -} - -#[test] -fn test_vec_access() { - let collections = Collections { - items: vec![10, 20, 30, 40, 50], - queue: VecDeque::new(), - }; - - // items() returns container; items_at(index) returns element at index - let container_kp = Collections::items(); - assert_eq!(container_kp.get(&collections).map(|v| v.len()), Some(5)); - - let first_kp = Collections::items_at(0); - assert_eq!(first_kp.get(&collections), Some(&10)); -} - -#[test] -fn test_vec_mutable() { - let mut collections = Collections { - items: vec![1, 2, 3, 4, 5], - queue: VecDeque::new(), - }; - - // Mutate first element through items_at(index) - let items_at_kp = Collections::items_at(0); - items_at_kp.get_mut(&mut collections).map(|v| *v = 200); - - assert_eq!(collections.items[0], 200); -} - -#[test] -fn test_vecdeque_access() { - let mut queue = VecDeque::new(); - queue.push_back(1.1); - queue.push_back(2.2); - queue.push_back(3.3); - - let collections = Collections { - items: vec![], - queue, - }; - // queue() returns container; queue_at(index) returns element at index - let front_kp = Collections::queue_at(0); - assert_eq!(front_kp.get(&collections), Some(&1.1)); -} - -#[test] -fn test_cow_access() { - let data = WithCow { - cow_owned: Cow::Owned("owned".to_string()), - cow_borrowed: Cow::Owned("borrowed".to_string()), - opt_cow: Some(Cow::Owned("optional".to_string())), - }; - - let cow_owned_kp = WithCow::cow_owned(); - 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") - ); - - let opt_cow_kp = WithCow::opt_cow(); - assert_eq!(opt_cow_kp.get(&data).map(|s| s.as_str()), Some("optional")); -} - -#[test] -fn test_cow_mutable() { - let mut data = WithCow { - cow_owned: Cow::Owned("original".to_string()), - cow_borrowed: Cow::Owned("borrowed".to_string()), - opt_cow: Some(Cow::Owned("opt_original".to_string())), - }; - - let cow_owned_kp = WithCow::cow_owned(); - 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") - ); -} - -#[test] -fn test_atomic_types() { - let mut data = WithAtomics { - counter: AtomicI32::new(42), - flags: AtomicU64::new(0xFF), - }; - - 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); - assert_eq!(data.counter.load(Ordering::SeqCst), 100); - - let flags_kp = WithAtomics::flags(); - assert_eq!(flags_kp.get(&data).unwrap().load(Ordering::SeqCst), 0xFF); -} - -#[test] -fn test_option_atomic() { - let mut data = WithOptionAtomic { - opt_counter: Some(AtomicI32::new(10)), - }; - let kp = WithOptionAtomic::opt_counter(); - assert_eq!(kp.get(&data).unwrap().load(Ordering::SeqCst), 10); - kp.get_mut(&mut data).unwrap().store(20, Ordering::SeqCst); - assert_eq!(data.opt_counter.unwrap().load(Ordering::SeqCst), 20); - - let data_none = WithOptionAtomic { opt_counter: None }; - assert!(kp.get(&data_none).is_none()); -} - -#[test] -fn test_cow_option_none() { - let data = WithCow { - cow_owned: Cow::Owned("x".to_string()), - cow_borrowed: Cow::Owned("y".to_string()), - opt_cow: None, - }; - - let opt_cow_kp = WithCow::opt_cow(); - assert_eq!(opt_cow_kp.get(&data), None); -} - -#[test] -fn test_hashmap_at() { - let mut users = HashMap::new(); - users.insert("alice".to_string(), 100); - users.insert("bob".to_string(), 200); - let data = WithMaps { - users: users.clone(), - cache: BTreeMap::new(), - }; - - 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 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)); -} - -#[test] -fn test_btreemap_at() { - let mut cache = BTreeMap::new(); - cache.insert(1u64, "one".to_string()); - cache.insert(2u64, "two".to_string()); - let data = WithMaps { - users: HashMap::new(), - cache: cache.clone(), - }; - - 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 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())); -} - -#[test] -fn test_box_access() { - let smart = SmartPointers { - boxed: Box::new("boxed_value".to_string()), - rc: std::rc::Rc::new(42), - arc: std::sync::Arc::new("arc_value".to_string()), - }; - - let boxed_kp = SmartPointers::boxed(); - assert_eq!( - boxed_kp.get(&smart).map(|s| s.as_str()), - Some("boxed_value") - ); -} - -#[test] -fn test_box_mutable() { - let mut smart = SmartPointers { - boxed: Box::new("original".to_string()), - rc: std::rc::Rc::new(1), - arc: std::sync::Arc::new("test".to_string()), - }; - - let boxed_kp = SmartPointers::boxed(); - boxed_kp - .get_mut(&mut smart) - .map(|s| *s = "modified".to_string()); - - assert_eq!(smart.boxed.as_str(), "modified"); -} - -#[test] -fn test_rc_access() { - let mut smart = SmartPointers { - boxed: Box::new("test".to_string()), - rc: std::rc::Rc::new(42), - arc: std::sync::Arc::new("test".to_string()), - }; - - let rc_kp = SmartPointers::rc(); - assert_eq!(rc_kp.get(&smart), Some(&42)); - - // Test mutable access when Rc has only one reference - rc_kp.get_mut(&mut smart).map(|v| *v = 100); - assert_eq!(*smart.rc, 100); -} - -#[test] -fn test_arc_access() { - let mut smart = SmartPointers { - boxed: Box::new("test".to_string()), - rc: std::rc::Rc::new(1), - arc: std::sync::Arc::new("original".to_string()), - }; - - let arc_kp = SmartPointers::arc(); - assert_eq!(arc_kp.get(&smart).map(|s| s.as_str()), Some("original")); - - // Test mutable access when Arc has only one reference - arc_kp - .get_mut(&mut smart) - .map(|v| *v = "modified".to_string()); - assert_eq!(smart.arc.as_str(), "modified"); -} - -#[test] -fn test_rc_no_mut_with_multiple_refs() { - let rc = std::rc::Rc::new(42); - let rc_clone = rc.clone(); // Now there are 2 references - - let mut smart = SmartPointers { - boxed: Box::new("test".to_string()), - rc, - arc: std::sync::Arc::new("test".to_string()), - }; - - let rc_kp = SmartPointers::rc(); - - // Should return None because there are multiple references - assert_eq!(rc_kp.get_mut(&mut smart), None); - - // Cleanup - drop(rc_clone); -} - -#[test] -fn test_arc_no_mut_with_multiple_refs() { - let arc = std::sync::Arc::new("test".to_string()); - let arc_clone = arc.clone(); // Now there are 2 references - - let mut smart = SmartPointers { - boxed: Box::new("test".to_string()), - rc: std::rc::Rc::new(1), - arc, - }; - - let arc_kp = SmartPointers::arc(); - - // Should return None because there are multiple references - assert_eq!(arc_kp.get_mut(&mut smart), None); - - // Cleanup - drop(arc_clone); -} - -#[test] -fn test_std_mutex_with_lockkp() { - use std::sync::Mutex; - - let locks = WithLocks { - std_mutex: Mutex::new(99), - std_rwlock: std::sync::RwLock::new("test".to_string()), - }; - - // Get keypath to mutex - let mutex_kp = WithLocks::std_mutex(); - let rwlock_kp = WithLocks::std_rwlock(); - // 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)); - - let lock_kp = LockKp::new(mutex_kp, rust_key_paths::StdMutexAccess::new(), next); - - // Access through lock - let value = lock_kp.get(&locks); - assert_eq!(value, Some(&99)); -} - -#[tokio::test] -async fn test_tokio_rwlock_async_kp() { - let root = WithTokioLocks { - data: Arc::new(tokio::sync::RwLock::new(vec![1, 2, 3, 4, 5])), - }; - - // data() returns KpType to container - let container_kp = WithTokioLocks::data(); - let arc_ref = container_kp.get(&root); - assert!(arc_ref.is_some()); - - // data_async() returns AsyncLockKpRwLockFor - use .get(&root).await for async access - let async_kp = WithTokioLocks::data_async(); - let value = async_kp.get(&root).await; - assert!(value.is_some()); - assert_eq!(value.unwrap().len(), 5); -} - -#[tokio::test] -async fn test_option_tokio_rwlock_async_kp() { - let root_some = WithOptionTokioLocks { - data: Some(Arc::new(tokio::sync::RwLock::new(42))), - }; - let root_none = WithOptionTokioLocks { data: None }; - - // data_async() - when Some, returns the value - let async_kp = WithOptionTokioLocks::data_async(); - let value = async_kp.get(&root_some).await; - assert!(value.is_some()); - assert_eq!(*value.unwrap(), 42); - - // When None, returns None - let value_none = async_kp.get(&root_none).await; - assert!(value_none.is_none()); -} diff --git a/key-paths-derive/tests/enum_complex_test.rs b/key-paths-derive/tests/enum_complex_test.rs deleted file mode 100644 index c6eb40f..0000000 --- a/key-paths-derive/tests/enum_complex_test.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Test enum with complex containers like Arc> (reusing struct prior art) - -use key_paths_derive::Kp; -use std::sync::Arc; -use std::sync::atomic::{AtomicI32, Ordering}; - -#[derive(Debug, Kp)] -enum Message { - Text(String), - Data(Arc>), - /// Arc> - has tokio_data_async() - TokioData(Arc>), - /// Arc> - has parking_data_lock() - ParkingData(Arc>), - /// std::sync::atomic::AtomicI32 - Counter(std::sync::atomic::AtomicI32), - Empty, -} - -#[test] -fn test_enum_arc_rwlock() { - let msg = Message::Data(Arc::new(std::sync::RwLock::new("hello".to_string()))); - let data_kp = Message::data(); - let arc = data_kp.get(&msg); - assert!(arc.is_some()); - - let lock_kp = Message::data_lock(); - let value = lock_kp.get(&msg).unwrap(); - assert_eq!(value.as_str(), "hello"); -} - -#[test] -fn test_enum_text() { - let msg = Message::Text("hi".to_string()); - let text_kp = Message::text(); - assert_eq!(text_kp.get(&msg), Some(&"hi".to_string())); -} - -#[test] -fn test_enum_empty() { - let msg = Message::Empty; - let empty_kp = Message::empty(); - assert!(empty_kp.get(&msg).is_some()); -} - -#[tokio::test] -async fn test_enum_tokio_async() { - 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()); - - let kp = Message::tokio_data_async(); - let guard = kp.get(&msg).await.unwrap(); - assert_eq!(guard.as_str(), "async_hello"); -} - -#[test] -fn test_enum_atomic() { - let msg = Message::Counter(AtomicI32::new(99)); - let kp = Message::counter(); - let atomic = kp.get(&msg).unwrap(); - assert_eq!(atomic.load(Ordering::SeqCst), 99); -} - -#[test] -fn test_enum_parking_lot() { - 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()); - - let lock_kp = Message::parking_data_lock(); - let guard = lock_kp.get(&msg).unwrap(); - assert_eq!(guard.as_str(), "parking_hello"); -} diff --git a/key-paths-derive/tests/wrapper_types_test.rs b/key-paths-derive/tests/wrapper_types_test.rs deleted file mode 100644 index a1658b7..0000000 --- a/key-paths-derive/tests/wrapper_types_test.rs +++ /dev/null @@ -1,509 +0,0 @@ -use key_paths_derive::{Akp, Kp, Pkp}; -use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; -use std::rc::Rc; -use std::sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock}; - -#[derive(Kp)] -struct AllWrapperTypes { - // Basic types - basic: String, - - // Option - opt_string: Option, - - // Vec - vec_numbers: Vec, - - // Box - boxed_value: Box, - - // Arc/Rc - arc_value: Arc, - rc_value: Rc, - - // HashMap and BTreeMap - hash_map: HashMap, - btree_map: BTreeMap, - - // Sets - hash_set: HashSet, - btree_set: BTreeSet, - - // VecDeque and LinkedList - vec_deque: VecDeque, - linked_list: LinkedList, - - // BinaryHeap - binary_heap: BinaryHeap, - - // Result - result_value: Result, - - // Mutex and RwLock - mutex_value: StdMutex, - rwlock_value: StdRwLock, -} - -#[test] -fn test_basic_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test basic type - let basic_kp = AllWrapperTypes::basic(); - assert_eq!(basic_kp.get(&data), Some(&"hello".to_string())); -} - -#[test] -fn test_all_wrapper_types_identity() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Identity keypath returns the struct itself - let identity_kp = AllWrapperTypes::identity(); - let result = identity_kp.get(&data); - assert!(result.is_some()); - assert!(std::ptr::eq(result.unwrap(), &data)); - - // identity_typed works as well - let identity_typed_kp = - AllWrapperTypes::identity_typed::<&AllWrapperTypes, &mut AllWrapperTypes>(); - let result_typed = identity_typed_kp.get(&data); - assert!(result_typed.is_some()); -} - -#[test] -fn test_option_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Option - should unwrap and access inner String - let opt_kp = AllWrapperTypes::opt_string(); - assert_eq!(opt_kp.get(&data), Some(&"world".to_string())); -} - -#[test] -fn test_option_none() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: None, - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Option None - should return None - let opt_kp = AllWrapperTypes::opt_string(); - assert_eq!(opt_kp.get(&data), None); -} - -#[test] -fn test_vec_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // vec_numbers() returns container; vec_numbers_at(index) returns element at index - let vec_container_kp = AllWrapperTypes::vec_numbers(); - assert_eq!(vec_container_kp.get(&data).map(|v| v.len()), Some(3)); - - let vec_kp = AllWrapperTypes::vec_numbers_at(0); - assert_eq!(vec_kp.get(&data), Some(&1)); -} - -#[test] -fn test_box_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Box - should deref to inner value (returns &i32, not &Box) - let box_kp = AllWrapperTypes::boxed_value(); - let value = box_kp.get(&data); - assert_eq!(value, Some(&42)); - - // Verify the correct type signature - let _typed: rust_key_paths::KpType<'static, AllWrapperTypes, i32> = box_kp; -} - -#[test] -fn test_arc_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Arc - should deref to inner value - let arc_kp = AllWrapperTypes::arc_value(); - assert_eq!(arc_kp.get(&data), Some(&"arc".to_string())); -} - -#[test] -fn test_result_type() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Result - should access Ok value - let result_kp = AllWrapperTypes::result_value(); - assert_eq!(result_kp.get(&data), Some(&"success".to_string())); -} - -#[test] -fn test_result_err() { - let data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Err("error".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test Result Err - should return None - let result_kp = AllWrapperTypes::result_value(); - assert_eq!(result_kp.get(&data), None); -} - -#[test] -fn test_mutable_basic_type() { - let mut data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test mutable access to basic type - let basic_kp = AllWrapperTypes::basic(); - basic_kp - .get_mut(&mut data) - .map(|v| *v = "modified".to_string()); - assert_eq!(data.basic, "modified"); -} - -#[test] -fn test_mutable_option_type() { - let mut data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test mutable access to Option inner value - let opt_kp = AllWrapperTypes::opt_string(); - opt_kp - .get_mut(&mut data) - .map(|v| *v = "modified".to_string()); - assert_eq!(data.opt_string, Some("modified".to_string())); -} - -#[test] -fn test_mutable_vec_type() { - let mut data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test mutable access to Vec first element via vec_numbers_at(index) - let vec_kp = AllWrapperTypes::vec_numbers_at(0); - vec_kp.get_mut(&mut data).map(|v| *v = 99); - assert_eq!(data.vec_numbers[0], 99); -} - -#[test] -fn test_mutable_box_type() { - let mut data = AllWrapperTypes { - basic: "hello".to_string(), - opt_string: Some("world".to_string()), - vec_numbers: vec![1, 2, 3], - boxed_value: Box::new(42), - arc_value: Arc::new("arc".to_string()), - rc_value: Rc::new("rc".to_string()), - hash_map: HashMap::new(), - btree_map: BTreeMap::new(), - hash_set: HashSet::new(), - btree_set: BTreeSet::new(), - vec_deque: VecDeque::new(), - linked_list: LinkedList::new(), - binary_heap: BinaryHeap::new(), - result_value: Ok("success".to_string()), - mutex_value: StdMutex::new(100), - rwlock_value: StdRwLock::new("locked".to_string()), - }; - - // Test mutable access to Box inner value - let box_kp = AllWrapperTypes::boxed_value(); - box_kp.get_mut(&mut data).map(|v| *v = 99); - assert_eq!(*data.boxed_value, 99); -} - -#[derive(Kp, Pkp, Akp, Debug, PartialEq)] -enum MyEnum { - Unit, - Single(String), - Tuple(i32, String), - Named { x: i32, y: String }, -} - -#[test] -fn test_enum_unit_variant() { - let e = MyEnum::Unit; - let unit_kp = MyEnum::unit(); - assert!(unit_kp.get(&e).is_some()); - - let e2 = MyEnum::Single("test".to_string()); - assert!(unit_kp.get(&e2).is_none()); -} - -#[test] -fn test_enum_single_variant() { - let e = MyEnum::Single("hello".to_string()); - let single_kp = MyEnum::single(); - assert_eq!(single_kp.get(&e), Some(&"hello".to_string())); - - let e2 = MyEnum::Unit; - assert!(single_kp.get(&e2).is_none()); -} - -#[test] -fn test_enum_tuple_variant() { - let e = MyEnum::Tuple(42, "world".to_string()); - let tuple_kp = MyEnum::tuple(); - assert_eq!(tuple_kp.get(&e), Some(&e)); - - let e2 = MyEnum::Unit; - assert!(tuple_kp.get(&e2).is_none()); -} - -#[test] -fn test_enum_named_variant() { - let e = MyEnum::Named { - x: 42, - y: "test".to_string(), - }; - let named_kp = MyEnum::named(); - assert_eq!(named_kp.get(&e), Some(&e)); - - let e2 = MyEnum::Unit; - assert!(named_kp.get(&e2).is_none()); -} - -#[test] -fn test_enum_partial_kps() { - let kps = MyEnum::partial_kps(); - assert_eq!(kps.len(), 4); // unit, single, tuple, named -} - -#[test] -fn test_enum_any_kps() { - let kps = MyEnum::any_kps(); - assert_eq!(kps.len(), 4); // unit, single, tuple, named -} - -#[derive(Kp)] -struct TupleStruct(String, i32, Option); - -#[test] -fn test_tuple_struct_fields() { - let ts = TupleStruct("hello".to_string(), 42, Some(true)); - - // Test field 0 (String) - let f0_kp = TupleStruct::f0(); - assert_eq!(f0_kp.get(&ts), Some(&"hello".to_string())); - - // Test field 1 (i32) - let f1_kp = TupleStruct::f1(); - assert_eq!(f1_kp.get(&ts), Some(&42)); - - // Test field 2 (Option - unwraps to bool) - let f2_kp = TupleStruct::f2(); - assert_eq!(f2_kp.get(&ts), Some(&true)); -} - -#[test] -fn test_tuple_struct_mutable() { - let mut ts = TupleStruct("hello".to_string(), 42, Some(true)); - - // Modify field 0 - let f0_kp = TupleStruct::f0(); - f0_kp.get_mut(&mut ts).map(|v| *v = "modified".to_string()); - assert_eq!(ts.0, "modified"); - - // Modify field 1 - let f1_kp = TupleStruct::f1(); - f1_kp.get_mut(&mut ts).map(|v| *v = 99); - assert_eq!(ts.1, 99); - - // Modify field 2 (Option inner value) - let f2_kp = TupleStruct::f2(); - f2_kp.get_mut(&mut ts).map(|v| *v = false); - assert_eq!(ts.2, Some(false)); -} diff --git a/src/async_lock.rs b/src/async_lock.rs index 50a32a6..ecc117d 100644 --- a/src/async_lock.rs +++ b/src/async_lock.rs @@ -29,10 +29,8 @@ //! - Only clones `PhantomData` which is zero-sized //! - Compiled away completely - zero runtime cost -use crate::{Kp, KpTrait}; +use crate::{AccessorTrait, Kp, KpTrait}; use async_trait::async_trait; -use std::sync::Arc; - // Re-export tokio sync types for convenience #[cfg(feature = "tokio")] pub use tokio::sync::{Mutex as TokioMutex, RwLock as TokioRwLock}; @@ -42,7 +40,7 @@ pub use tokio::sync::{Mutex as TokioMutex, RwLock as TokioRwLock}; // ============================================================================= // // - AsyncLockLike: One "step" through a lock. Given a Lock (e.g. -// Arc>), it async yields Inner (e.g. &T). Used by the `mid` field of +// std::sync::Arc>), it async yields Inner (e.g. &T). Used by the `mid` field of // AsyncLockKp to go from "container" to "value inside the lock". Implemented // by TokioMutexAccess, TokioRwLockAccess, etc. // @@ -140,12 +138,13 @@ where MutValue: std::borrow::BorrowMut, G: Fn(Root) -> Option, S: Fn(MutRoot) -> Option, + // Kp: AccessorTrait { fn sync_get(&self, root: Root) -> Option { - self.get(root) + (self.get)(root) } fn sync_get_mut(&self, root: MutRoot) -> Option { - self.get_mut(root) + (self.set)(root) } } @@ -235,16 +234,16 @@ pub trait AsyncKeyPathLike { async fn get_mut(&self, root: MutRoot) -> Option; } -/// An async keypath that handles async locked values (e.g., Arc>) +/// An async keypath that handles async locked values (e.g., std::sync::Arc>) /// /// Structure: -/// - `prev`: Keypath from Root to Lock container (e.g., Arc>) +/// - `prev`: Keypath from Root to Lock container (e.g., std::sync::Arc>) /// - `mid`: Async lock access handler that goes from Lock to Inner value /// - `next`: Keypath from Inner value to final Value /// /// # Type Parameters /// - `R`: Root type (base) -/// - `Lock`: Lock container type (e.g., Arc>) +/// - `Lock`: Lock container type (e.g., std::sync::Arc>) /// - `Mid`: The type inside the lock /// - `V`: Final value type /// - Rest are the same generic parameters as Kp @@ -382,7 +381,7 @@ where where Lock: Clone, { - // SHALLOW CLONE: For Arc>, only increments Arc refcount + // SHALLOW CLONE: For std::sync::Arc>, only increments Arc refcount // The actual data T is NOT cloned let lock_value = (self.prev.get)(root)?; let lock: &Lock = lock_value.borrow(); @@ -401,7 +400,7 @@ where where Lock: Clone, { - // SHALLOW CLONE: For Arc>, only increments Arc refcount + // SHALLOW CLONE: For std::sync::Arc>, only increments Arc refcount let mut lock_value = (self.prev.set)(root)?; let lock: &mut Lock = lock_value.borrow_mut(); let mut lock_clone = lock.clone(); // SHALLOW: Arc refcount++ @@ -468,13 +467,13 @@ where Lock: Clone, F: FnOnce(&mut V), { - // SHALLOW CLONE: For Arc>, only increments Arc refcount + // SHALLOW CLONE: For std::sync::Arc>, only increments Arc refcount let lock_value = (self.prev.get)(root).ok_or("Failed to get lock from root")?; let lock: &Lock = lock_value.borrow(); let lock_clone = lock.clone(); // SHALLOW: Arc refcount++ // Async lock and get the mid value - let mut mid_value = self + let mid_value = self .mid .lock_read(&lock_clone) .await @@ -499,7 +498,7 @@ where /// /// # Example /// ```ignore - /// // Root -> Arc> -> Inner -> field + /// // Root -> std::sync::Arc> -> Inner -> field /// let async_kp = AsyncLockKp::new(root_to_lock, TokioMutexAccess::new(), lock_to_inner); /// let field_kp = Kp::new(|inner: &Inner| Some(&inner.field), |inner: &mut Inner| Some(&mut inner.field)); /// let chained = async_kp.then(field_kp); @@ -695,7 +694,7 @@ where /// /// # Example /// ```ignore - /// // Root -> Arc> -> Container -> Arc> -> Value + /// // Root -> std::sync::Arc> -> Container -> std::sync::Arc> -> Value /// let async_kp1 = AsyncLockKp::new(...); // Root -> Container /// let async_kp2 = AsyncLockKp::new(...); // Container -> Value /// let chained = async_kp1.then_async(async_kp2); @@ -1803,7 +1802,7 @@ where // ============================================================================ #[cfg(feature = "tokio")] -/// Async lock access implementation for Arc> +/// Async lock access implementation for std::sync::Arc> /// /// # Cloning Behavior /// @@ -1833,11 +1832,11 @@ impl Default for TokioMutexAccess { // Implementation for immutable access #[cfg(feature = "tokio")] #[async_trait] -impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a T> +impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a T> for TokioMutexAccess { #[inline] - async fn lock_read(&self, lock: &Arc>) -> Option<&'a T> { + async fn lock_read(&self, lock: &std::sync::Arc>) -> Option<&'a T> { // SHALLOW CLONE: Only Arc refcount is incremented let guard = lock.lock().await; let ptr = &*guard as *const T; @@ -1845,7 +1844,7 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a } #[inline] - async fn lock_write(&self, lock: &mut Arc>) -> Option<&'a T> { + async fn lock_write(&self, lock: &mut std::sync::Arc>) -> Option<&'a T> { let guard = lock.lock().await; let ptr = &*guard as *const T; unsafe { Some(&*ptr) } @@ -1855,11 +1854,11 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a // Implementation for mutable access #[cfg(feature = "tokio")] #[async_trait] -impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a mut T> +impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a mut T> for TokioMutexAccess { #[inline] - async fn lock_read(&self, lock: &Arc>) -> Option<&'a mut T> { + async fn lock_read(&self, lock: &std::sync::Arc>) -> Option<&'a mut T> { // SHALLOW CLONE: Only Arc refcount is incremented let mut guard = lock.lock().await; let ptr = &mut *guard as *mut T; @@ -1867,7 +1866,7 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a } #[inline] - async fn lock_write(&self, lock: &mut Arc>) -> Option<&'a mut T> { + async fn lock_write(&self, lock: &mut std::sync::Arc>) -> Option<&'a mut T> { let mut guard = lock.lock().await; let ptr = &mut *guard as *mut T; unsafe { Some(&mut *ptr) } @@ -1879,7 +1878,7 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a // ============================================================================ #[cfg(feature = "tokio")] -/// Async lock access implementation for Arc> +/// Async lock access implementation for std::sync::Arc> /// /// # Cloning Behavior /// @@ -1918,17 +1917,17 @@ impl Clone for TokioRwLockAccess { // Implementation for immutable access (read lock) #[cfg(feature = "tokio")] #[async_trait] -impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a T> +impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a T> for TokioRwLockAccess { - async fn lock_read(&self, lock: &Arc>) -> Option<&'a T> { + async fn lock_read(&self, lock: &std::sync::Arc>) -> Option<&'a T> { // SHALLOW CLONE: Only Arc refcount is incremented let guard = lock.read().await; let ptr = &*guard as *const T; unsafe { Some(&*ptr) } } - async fn lock_write(&self, lock: &mut Arc>) -> Option<&'a T> { + async fn lock_write(&self, lock: &mut std::sync::Arc>) -> Option<&'a T> { // For immutable access, use read lock let guard = lock.read().await; let ptr = &*guard as *const T; @@ -1939,17 +1938,17 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &' // Implementation for mutable access (write lock) #[cfg(feature = "tokio")] #[async_trait] -impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a mut T> +impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &'a mut T> for TokioRwLockAccess { - async fn lock_read(&self, lock: &Arc>) -> Option<&'a mut T> { + async fn lock_read(&self, lock: &std::sync::Arc>) -> Option<&'a mut T> { // For mutable access, use write lock let mut guard = lock.write().await; let ptr = &mut *guard as *mut T; unsafe { Some(&mut *ptr) } } - async fn lock_write(&self, lock: &mut Arc>) -> Option<&'a mut T> { + async fn lock_write(&self, lock: &mut std::sync::Arc>) -> Option<&'a mut T> { // SHALLOW CLONE: Only Arc refcount is incremented let mut guard = lock.write().await; let ptr = &mut *guard as *mut T; @@ -1965,7 +1964,7 @@ impl<'a, T: 'static + Send + Sync> AsyncLockLike>, &' // with a root, not when the keypath is constructed. #[cfg(feature = "tokio")] -/// Type alias for AsyncLockKp over Arc>. Use with derive macro's `_async()` methods. +/// Type alias for AsyncLockKp over std::sync::Arc>. Use with derive macro's `_async()` methods. pub type AsyncLockKpMutexFor = AsyncLockKp< Root, Lock, @@ -1987,7 +1986,7 @@ pub type AsyncLockKpMutexFor = AsyncLockKp< >; #[cfg(feature = "tokio")] -/// Type alias for AsyncLockKp over Arc>. Use with derive macro's `_async()` methods. +/// Type alias for AsyncLockKp over std::sync::Arc>. Use with derive macro's `_async()` methods. pub type AsyncLockKpRwLockFor = AsyncLockKp< Root, Lock, @@ -2023,16 +2022,16 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>, + data: std::sync::Arc>, } let root = Root { - data: Arc::new(Mutex::new("hello".to_string())), + data: std::sync::Arc::new(Mutex::new("hello".to_string())), }; // Create AsyncLockKp let lock_kp = { - let prev: KpType>> = + 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)); @@ -2051,15 +2050,15 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>, + data: std::sync::Arc>, } let mut root = Root { - data: Arc::new(Mutex::new(42)), + data: std::sync::Arc::new(Mutex::new(42)), }; let lock_kp = { - let prev: KpType>> = + 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) @@ -2094,16 +2093,16 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>>, + data: std::sync::Arc>>, } let root = Root { - data: Arc::new(RwLock::new(vec![1, 2, 3, 4, 5])), + data: std::sync::Arc::new(RwLock::new(vec![1, 2, 3, 4, 5])), }; // Create AsyncLockKp with RwLock let lock_kp = { - let prev: KpType>>> = + let prev: KpType>>> = Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); let next: KpType, Vec> = Kp::new(|v: &Vec| Some(v), |v: &mut Vec| Some(v)); @@ -2122,16 +2121,16 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>, + data: std::sync::Arc>, } let root = Root { - data: Arc::new(RwLock::new(42)), + data: std::sync::Arc::new(RwLock::new(42)), }; // Create AsyncLockKp let lock_kp = { - let prev: KpType>> = + 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) @@ -2140,7 +2139,7 @@ mod tests { // 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>> = + 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) @@ -2171,7 +2170,7 @@ mod tests { #[derive(Clone)] struct Root { - level1: Arc>, + level1: std::sync::Arc>, } struct Level1 { @@ -2187,7 +2186,7 @@ mod tests { // Create structure with PanicOnClone let root = Root { - level1: Arc::new(Mutex::new(Level1 { + level1: std::sync::Arc::new(Mutex::new(Level1 { panic_data: PanicOnClone { data: "test".to_string(), }, @@ -2197,7 +2196,7 @@ mod tests { // Create AsyncLockKp let lock_kp = { - let prev: KpType>> = Kp::new( + let prev: KpType>> = Kp::new( |r: &Root| Some(&r.level1), |r: &mut Root| Some(&mut r.level1), ); @@ -2221,11 +2220,11 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>, + data: std::sync::Arc>, } let lock_kp = { - let prev: KpType>> = + 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)); @@ -2244,7 +2243,7 @@ mod tests { #[derive(Clone)] struct Root { - data: Arc>, + data: std::sync::Arc>, } #[derive(Clone)] @@ -2253,12 +2252,12 @@ mod tests { } let root = Root { - data: Arc::new(Mutex::new(Inner { value: 42 })), + data: std::sync::Arc::new(Mutex::new(Inner { value: 42 })), }; // Create AsyncLockKp to Inner let async_kp = { - let prev: KpType>> = + let prev: KpType>> = Kp::new(|r: &Root| Some(&r.data), |r: &mut Root| Some(&mut r.data)); let next: KpType = Kp::new(|i: &Inner| Some(i), |i: &mut Inner| Some(i)); AsyncLockKp::new(prev, TokioMutexAccess::new(), next) @@ -2281,23 +2280,23 @@ mod tests { #[derive(Clone)] struct Root { - lock1: Arc>, + lock1: std::sync::Arc>, } #[derive(Clone)] struct Container { - lock2: Arc>, + lock2: std::sync::Arc>, } let root = Root { - lock1: Arc::new(Mutex::new(Container { - lock2: Arc::new(Mutex::new(999)), + lock1: std::sync::Arc::new(Mutex::new(Container { + lock2: std::sync::Arc::new(Mutex::new(999)), })), }; // First AsyncLockKp: Root -> Container let async_kp1 = { - let prev: KpType>> = + let prev: KpType>> = Kp::new(|r: &Root| Some(&r.lock1), |r: &mut Root| Some(&mut r.lock1)); let next: KpType = Kp::new(|c: &Container| Some(c), |c: &mut Container| Some(c)); @@ -2306,7 +2305,7 @@ mod tests { // Second AsyncLockKp: Container -> i32 let async_kp2 = { - let prev: KpType>> = Kp::new( + let prev: KpType>> = Kp::new( |c: &Container| Some(&c.lock2), |c: &mut Container| Some(&mut c.lock2), ); @@ -2326,41 +2325,41 @@ mod tests { #[derive(Clone)] struct Root { - a: Arc>, + a: std::sync::Arc>, } #[derive(Clone)] struct Level1 { - b: Arc>, + b: std::sync::Arc>, } #[derive(Clone)] struct Level2 { - c: Arc>, + c: std::sync::Arc>, } let root = Root { - a: Arc::new(Mutex::new(Level1 { - b: Arc::new(Mutex::new(Level2 { - c: Arc::new(Mutex::new(42)), + a: std::sync::Arc::new(Mutex::new(Level1 { + b: std::sync::Arc::new(Mutex::new(Level2 { + c: std::sync::Arc::new(Mutex::new(42)), })), })), }; let kp1 = { - let prev: KpType>> = + let prev: KpType>> = Kp::new(|r: &Root| Some(&r.a), |r: &mut Root| Some(&mut r.a)); let next: KpType = Kp::new(|l: &Level1| Some(l), |l: &mut Level1| Some(l)); AsyncLockKp::new(prev, TokioMutexAccess::new(), next) }; let kp2 = { - let prev: KpType>> = + let prev: KpType>> = Kp::new(|l: &Level1| Some(&l.b), |l: &mut Level1| Some(&mut l.b)); let next: KpType = Kp::new(|l: &Level2| Some(l), |l: &mut Level2| Some(l)); AsyncLockKp::new(prev, TokioMutexAccess::new(), next) }; let kp3 = { - let prev: KpType>> = + let prev: KpType>> = Kp::new(|l: &Level2| Some(&l.c), |l: &mut Level2| Some(&mut l.c)); let next: KpType = Kp::new(|n: &i32| Some(n), |n: &mut i32| Some(n)); AsyncLockKp::new(prev, TokioMutexAccess::new(), next) diff --git a/src/lib.rs b/src/lib.rs index 8c8a739..aa1a8a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ // type Getter where Root: std::borrow::Borrow, Value: std::borrow::Borrow = fn(Root) -> Option; // type Setter = fn(&'r mut R) -> Option<&'r mut V>; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc}; // Export the lock module pub mod lock; @@ -1226,42 +1226,39 @@ where } } -pub trait AccessorTrait: - KpTrait +pub trait AccessorTrait { /// 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)) - } + 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)) - } + 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) - } + 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) - } + F: FnOnce() -> MutValue,; + // { + // self.get_mut(root).unwrap_or_else(f) + // } } -pub trait CoercionTrait: - KpTrait +pub trait CoercionTrait where Root: std::borrow::Borrow, Value: std::borrow::Borrow, @@ -1286,20 +1283,7 @@ 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))) - }, - ) - } + MutRoot: for<'a> From<&'a mut R>,; fn for_box<'a>( &self, @@ -1317,22 +1301,17 @@ 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())) - }, - ) - } + MutRoot: for<'b> From<&'b mut R>,; + + /// set fn is converting fn pointer to Fn closure + fn into_set(self) -> impl Fn(MutRoot) -> Option; + + /// get fn is converting fn pointer to Fn closure + fn into_get(self) -> impl Fn(Root) -> Option; + } -pub trait HOFTrait: +pub trait HofTrait: KpTrait where Root: std::borrow::Borrow, @@ -1778,15 +1757,6 @@ where 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, @@ -1813,6 +1783,15 @@ where move |root: MutRoot| (self.set)(root).and_then(|value| (next.set)(value)), ) } + + fn get(&self, root: Root) -> Option { + (self.get)(root) + } + + fn get_mut(&self, root: MutRoot) -> Option { + (self.set)(root) + } + } impl @@ -1826,10 +1805,83 @@ where 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.set)(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.set)(MutRoot::from(r.as_mut())) + }, + ) + } + + + /// set fn is converting fn pointer to Fn closure + #[inline] + fn into_set(self) -> impl Fn(MutRoot) -> Option { + self.set + } + + /// get fn is converting fn pointer to Fn closure + #[inline] + fn into_get(self) -> impl Fn(Root) -> Option { + self.get + } } impl - HOFTrait + HofTrait for Kp where Root: std::borrow::Borrow, @@ -1840,6 +1892,51 @@ where S: Fn(MutRoot) -> Option, { } + +impl + AccessorTrait + 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, +{ + /// 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.set)(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.set)(root).unwrap_or_else(f) + } +} + /// AKp (AnyKeyPath) - Hides both Root and Value types /// Most flexible keypath type for heterogeneous collections /// Uses dynamic dispatch and type checking at runtime @@ -1861,9 +1958,9 @@ where S: Fn(MutRoot) -> Option, { /// Getter closure: used by [Kp::get] for read-only access. - pub(crate) get: G, + pub get: G, /// Setter closure: used by [Kp::get_mut] for mutation. - pub(crate) set: S, + pub set: S, _p: std::marker::PhantomData<(R, V, Root, Value, MutRoot, MutValue)>, } @@ -1889,6 +1986,7 @@ where { } + impl Kp where Root: std::borrow::Borrow, @@ -1906,15 +2004,15 @@ where } } - #[inline] - pub fn get(&self, root: Root) -> Option { - (self.get)(root) - } + // #[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 get_mut(&self, root: MutRoot) -> Option { + // (self.set)(root) + // } #[inline] pub fn then( @@ -2070,12 +2168,12 @@ where /// Extract the variant from an enum (returns None if wrong variant) pub fn get(&self, enum_value: Root) -> Option { - self.extractor.get(enum_value) + (self.extractor.get)(enum_value) } /// Extract the variant mutably from an enum (returns None if wrong variant) pub fn get_mut(&self, enum_value: MutRoot) -> Option { - self.extractor.get_mut(enum_value) + (self.extractor.set)(enum_value) } /// Embed a value into the enum variant @@ -2585,15 +2683,15 @@ mod tests { .get_mut(&mut instance) .unwrap(); *wres = String::from("a3 changed successfully"); - let res = TestKP::f().then(TestKP2::a()).get(&instance); + let res = (TestKP::f().then(TestKP2::a()).get)(&instance); println!("{:?}", res); - let res = TestKP::f().then(TestKP2::identity()).get(&instance); + let res = (TestKP::f().then(TestKP2::identity()).get)(&instance); println!("{:?}", res); - let res = kp.get(&instance); + let res = (kp.get)(&instance); println!("{:?}", res); let new_kp_from_hashmap = TestKP::g(0).then(TestKP2::a()); - println!("{:?}", new_kp_from_hashmap.get(&instance)); + println!("{:?}", (new_kp_from_hashmap.get)(&instance)); } // #[test] @@ -2797,7 +2895,7 @@ mod tests { let box_kp = kp_box(); // Test get - assert_eq!(box_kp.get(&boxed), Some(&"value".to_string())); + assert_eq!((box_kp.get)(&boxed), Some(&"value".to_string())); // Test get_mut if let Some(val) = box_kp.get_mut(&mut boxed_mut) { @@ -2814,7 +2912,7 @@ mod tests { let arc_kp = kp_arc(); // Test get - assert_eq!(arc_kp.get(&arc), Some(&"value".to_string())); + assert_eq!((arc_kp.get)(&arc), Some(&"value".to_string())); // Test get_mut (only works if Arc has no other references) if let Some(val) = arc_kp.get_mut(&mut arc_mut) { @@ -2852,7 +2950,7 @@ mod tests { let ok_kp_base = ok_kp.into_kp(); let composed = ok_kp_base.then(inner_kp); - assert_eq!(composed.get(&result), Some(&"nested".to_string())); + assert_eq!((composed.get)(&result), Some(&"nested".to_string())); } #[test] @@ -3143,17 +3241,17 @@ mod tests { 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)); + assert_eq!((len_kp.get)(&user), Some(5)); // Map age to double let age_kp = KpType::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); let double_age_kp = age_kp.map(|age: &i32| age * 2); - assert_eq!(double_age_kp.get(&user), Some(60)); + assert_eq!((double_age_kp.get)(&user), Some(60)); // Map to boolean let is_adult_kp = age_kp.map(|age: &i32| *age >= 18); - assert_eq!(is_adult_kp.get(&user), Some(true)); + assert_eq!((is_adult_kp.get)(&user), Some(true)); } #[test] @@ -3177,15 +3275,15 @@ mod tests { let age_kp = KpType::new(|u: &User| Some(&u.age), |u: &mut User| Some(&mut u.age)); let adult_age_kp = age_kp.filter(|age: &i32| *age >= 18); - assert_eq!(adult_age_kp.get(&adult), Some(&30)); - assert_eq!(adult_age_kp.get(&minor), None); + assert_eq!((adult_age_kp.get)(&adult), Some(&30)); + assert_eq!((adult_age_kp.get)(&minor), None); // Filter names by length let name_kp = KpType::new(|u: &User| Some(&u.name), |u: &mut User| Some(&mut u.name)); let short_name_kp = name_kp.filter(|name: &String| name.len() <= 4); - assert_eq!(short_name_kp.get(&minor), Some(&"Bob".to_string())); - assert_eq!(short_name_kp.get(&adult), None); + assert_eq!((short_name_kp.get)(&minor), Some(&"Bob".to_string())); + assert_eq!((short_name_kp.get)(&adult), None); } #[test] @@ -3211,7 +3309,7 @@ mod tests { // Filter for high averages let high_avg_kp = avg_kp.filter(|avg: &i32| *avg >= 85); - assert_eq!(high_avg_kp.get(&user), Some(87)); // (85+92+78+95)/4 = 87.5 -> 87 + assert_eq!((high_avg_kp.get)(&user), Some(87)); // (85+92+78+95)/4 = 87.5 -> 87 } #[test] @@ -3245,7 +3343,7 @@ mod tests { let ok_kp = enum_ok::(); let positive_kp = ok_kp.filter(|x: &i32| *x > 0); - assert_eq!(positive_kp.get(&ok_result1), Some(&42)); + assert_eq!((positive_kp.extractor.get)(&ok_result1), Some(&42)); assert_eq!(positive_kp.get(&ok_result2), None); // Negative number filtered out assert_eq!(positive_kp.get(&err_result), None); // Err variant @@ -3361,8 +3459,8 @@ mod tests { let first_char_kp = middle_kp .filter_map(|opt: &Option| opt.as_ref().and_then(|s| s.chars().next())); - assert_eq!(first_char_kp.get(&user_with), Some('M')); - assert_eq!(first_char_kp.get(&user_without), None); + assert_eq!((first_char_kp.get)(&user_with), Some('M')); + assert_eq!((first_char_kp.get)(&user_without), None); } #[test] @@ -3383,7 +3481,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); + let result = (name_kp.get)(&user); assert_eq!(result, Some(&"Akash".to_string())); // The inspect method works, it just requires Copy closures diff --git a/src/lock.rs b/src/lock.rs index 45b5fbe..33686b3 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -286,7 +286,7 @@ where /// No longer needs `Lock: Clone` because `lock_write` now takes `&Lock` instead of `&mut Lock` #[inline] pub fn get_mut(&self, root: MutRoot) -> Option { - (self.prev.set)(root).and_then(|mut lock_value| { + (self.prev.set)(root).and_then(|lock_value| { let lock: &Lock = lock_value.borrow(); self.mid .lock_write(lock) diff --git a/tests/integration_nine_nesting.rs b/tests/integration_nine_nesting.rs deleted file mode 100644 index fbae049..0000000 --- a/tests/integration_nine_nesting.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! Integration test: all 9 possible nesting combinations in one place. -//! -//! The 9 pairs (First → Second) are: -//! 1. Kp → Kp 2. Kp → LockKp 3. Kp → AsyncKp -//! 4. LockKp → Kp 5. LockKp → LockKp 6. LockKp → AsyncKp -//! 7. AsyncKp → Kp 8. AsyncKp → LockKp 9. AsyncKp → AsyncKp - -#![cfg(all(feature = "tokio", feature = "parking_lot"))] - -use rust_key_paths::async_lock::{AsyncLockKp, TokioMutexAccess}; -use rust_key_paths::lock::{ArcMutexAccess, LockKp, ParkingLotMutexAccess}; -use rust_key_paths::{Kp, KpType}; -use std::sync::{Arc, Mutex}; - -// ----------------------------------------------------------------------------- -// 1. Kp → Kp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root1 { - a: A1, -} -#[derive(Clone)] -struct A1 { - b: i32, -} - -// ----------------------------------------------------------------------------- -// 2. Kp → LockKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root2 { - m: Arc>, -} -#[derive(Clone)] -struct B2 { - x: i32, -} - -// ----------------------------------------------------------------------------- -// 3. Kp → AsyncKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root3 { - t: Arc>, -} -#[derive(Clone)] -struct C3 { - y: i32, -} - -// ----------------------------------------------------------------------------- -// 4. LockKp → Kp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root4 { - m: Arc>, -} -#[derive(Clone)] -struct D4 { - e: E4, -} -#[derive(Clone)] -struct E4 { - z: i32, -} - -// ----------------------------------------------------------------------------- -// 5. LockKp → LockKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root5 { - m1: Arc>, -} -#[derive(Clone)] -struct F5 { - m2: Arc>, -} -#[derive(Clone)] -struct G5 { - v: i32, -} - -// ----------------------------------------------------------------------------- -// 6. LockKp → AsyncKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root6 { - m: Arc>, -} -#[derive(Clone)] -struct H6 { - t: Arc>, -} -#[derive(Clone)] -struct I6 { - w: i32, -} - -// ----------------------------------------------------------------------------- -// 7. AsyncKp → Kp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root7 { - t: Arc>, -} -#[derive(Clone)] -struct J7 { - k: i32, -} - -// ----------------------------------------------------------------------------- -// 8. AsyncKp → LockKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root8 { - t: Arc>, -} -#[derive(Clone)] -struct L8 { - m: Arc>, -} -#[derive(Clone)] -struct M8 { - n: i32, -} - -// ----------------------------------------------------------------------------- -// 9. AsyncKp → AsyncKp -// ----------------------------------------------------------------------------- -#[derive(Clone)] -struct Root9 { - t1: Arc>, -} -#[derive(Clone)] -struct N9 { - t2: Arc>, -} -#[derive(Clone)] -struct P9 { - q: i32, -} - -#[tokio::test] -async fn all_nine_nesting_combinations() { - // 1. Kp → Kp - let root1 = Root1 { a: A1 { b: 1 } }; - let kp_ra: KpType = Kp::new(|r: &Root1| Some(&r.a), |r: &mut Root1| Some(&mut r.a)); - let kp_ab: KpType = Kp::new(|a: &A1| Some(&a.b), |a: &mut A1| Some(&mut a.b)); - let chain1 = kp_ra.then(kp_ab); - assert_eq!(chain1.get(&root1), Some(&1)); - - // 2. Kp → LockKp - let root2 = Root2 { - m: Arc::new(Mutex::new(B2 { x: 2 })), - }; - 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 next: KpType = Kp::new(|b: &B2| Some(&b.x), |b: &mut B2| Some(&mut b.x)); - LockKp::new(prev, ArcMutexAccess::new(), next) - }; - let chain2 = kp_rm.then_lock(lock_bx); - assert_eq!(chain2.get(&root2), Some(&2)); - - // 3. Kp → AsyncKp - let root3 = Root3 { - t: Arc::new(tokio::sync::Mutex::new(C3 { y: 3 })), - }; - let kp_rt: KpType>> = - Kp::new(|r: &Root3| Some(&r.t), |r: &mut Root3| Some(&mut r.t)); - let async_cy = { - let prev: KpType>, Arc>> = - Kp::new(|t: &_| Some(t), |t: &mut _| Some(t)); - let next: KpType = Kp::new(|c: &C3| Some(&c.y), |c: &mut C3| Some(&mut c.y)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let chain3 = kp_rt.then_async(async_cy); - assert_eq!(chain3.get(&root3).await, Some(&3)); - - // 4. LockKp → Kp - let root4 = Root4 { - m: Arc::new(Mutex::new(D4 { e: E4 { z: 4 } })), - }; - let lock_rd = { - let prev: KpType>> = - Kp::new(|r: &Root4| Some(&r.m), |r: &mut Root4| Some(&mut r.m)); - let next: KpType = Kp::new(|d: &D4| Some(d), |d: &mut D4| Some(d)); - LockKp::new(prev, ArcMutexAccess::new(), next) - }; - let kp_de: KpType = Kp::new(|d: &D4| Some(&d.e.z), |d: &mut D4| Some(&mut d.e.z)); - let chain4 = lock_rd.then(kp_de); - assert_eq!(chain4.get(&root4), Some(&4)); - - // 5. LockKp → LockKp - let root5 = Root5 { - m1: Arc::new(Mutex::new(F5 { - m2: Arc::new(Mutex::new(G5 { v: 5 })), - })), - }; - let lock_rf = { - let prev: KpType>> = - Kp::new(|r: &Root5| Some(&r.m1), |r: &mut Root5| Some(&mut r.m1)); - let next: KpType = Kp::new(|f: &F5| Some(f), |f: &mut F5| Some(f)); - LockKp::new(prev, ArcMutexAccess::new(), next) - }; - let lock_fg = { - let prev: KpType>> = - Kp::new(|f: &F5| Some(&f.m2), |f: &mut F5| Some(&mut f.m2)); - let next: KpType = Kp::new(|g: &G5| Some(&g.v), |g: &mut G5| Some(&mut g.v)); - LockKp::new(prev, ArcMutexAccess::new(), next) - }; - let chain5 = lock_rf.then_lock(lock_fg); - assert_eq!(chain5.get(&root5), Some(&5)); - - // 6. LockKp → AsyncKp - let root6 = Root6 { - m: Arc::new(Mutex::new(H6 { - t: Arc::new(tokio::sync::Mutex::new(I6 { w: 6 })), - })), - }; - let lock_rh = { - let prev: KpType>> = - Kp::new(|r: &Root6| Some(&r.m), |r: &mut Root6| Some(&mut r.m)); - let next: KpType = Kp::new(|h: &H6| Some(h), |h: &mut H6| Some(h)); - LockKp::new(prev, ArcMutexAccess::new(), next) - }; - let async_hi = { - let prev: KpType>> = - Kp::new(|h: &H6| Some(&h.t), |h: &mut H6| Some(&mut h.t)); - let next: KpType = Kp::new(|i: &I6| Some(&i.w), |i: &mut I6| Some(&mut i.w)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let chain6 = lock_rh.then_async(async_hi); - assert_eq!(chain6.get(&root6).await, Some(&6)); - - // 7. AsyncKp → Kp - let root7 = Root7 { - t: Arc::new(tokio::sync::Mutex::new(J7 { k: 7 })), - }; - let async_rj = { - let prev: KpType>> = - Kp::new(|r: &Root7| Some(&r.t), |r: &mut Root7| Some(&mut r.t)); - let next: KpType = Kp::new(|j: &J7| Some(j), |j: &mut J7| Some(j)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let kp_jk: KpType = Kp::new(|j: &J7| Some(&j.k), |j: &mut J7| Some(&mut j.k)); - let chain7 = async_rj.then(kp_jk); - assert_eq!(chain7.get(&root7).await, Some(&7)); - - // 8. AsyncKp → LockKp - let root8 = Root8 { - t: Arc::new(tokio::sync::Mutex::new(L8 { - m: Arc::new(parking_lot::Mutex::new(M8 { n: 8 })), - })), - }; - let async_rl = { - let prev: KpType>> = - Kp::new(|r: &Root8| Some(&r.t), |r: &mut Root8| Some(&mut r.t)); - let next: KpType = Kp::new(|l: &L8| Some(l), |l: &mut L8| Some(l)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let lock_mn = { - let prev: KpType>> = - Kp::new(|l: &L8| Some(&l.m), |l: &mut L8| Some(&mut l.m)); - let next: KpType = Kp::new(|m: &M8| Some(&m.n), |m: &mut M8| Some(&mut m.n)); - LockKp::new(prev, ParkingLotMutexAccess::new(), next) - }; - let chain8 = async_rl.then_lock(lock_mn); - assert_eq!(chain8.get(&root8).await, Some(&8)); - - // 9. AsyncKp → AsyncKp - let root9 = Root9 { - t1: Arc::new(tokio::sync::Mutex::new(N9 { - t2: Arc::new(tokio::sync::Mutex::new(P9 { q: 9 })), - })), - }; - let async_rn = { - let prev: KpType>> = - Kp::new(|r: &Root9| Some(&r.t1), |r: &mut Root9| Some(&mut r.t1)); - let next: KpType = Kp::new(|n: &N9| Some(n), |n: &mut N9| Some(n)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let async_pq = { - let prev: KpType>> = - Kp::new(|n: &N9| Some(&n.t2), |n: &mut N9| Some(&mut n.t2)); - let next: KpType = Kp::new(|p: &P9| Some(&p.q), |p: &mut P9| Some(&mut p.q)); - AsyncLockKp::new(prev, TokioMutexAccess::new(), next) - }; - let chain9 = async_rn.then_async(async_pq); - assert_eq!(chain9.get(&root9).await, Some(&9)); -} diff --git a/tests/integration_pin_future.rs b/tests/integration_pin_future.rs deleted file mode 100644 index 4923243..0000000 --- a/tests/integration_pin_future.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Integration test: `then_pin_future` composition for #[pin] Future fields. -//! -//! Uses Kp-derived `{field}_pin_future_kp()` with `Kp::then_pin_future` to await -//! pinned futures ergonomically, in the style of `then_async` for async locks. - -#![cfg(all(feature = "tokio", feature = "pin_project"))] - -use std::future::Future; -use std::pin::Pin; - -use key_paths_derive::Kp; -use pin_project::pin_project; -use rust_key_paths::{Kp, KpType}; - -#[pin_project] -#[derive(Kp)] -struct WithPinnedBoxFuture { - #[pin] - fut: Pin + Send>>, -} - -#[pin_project] -#[derive(Kp)] -struct Wrapper { - inner: WithPinnedBoxFuture, -} - -#[tokio::test] -async fn test_then_pin_future_identity() { - use std::future::ready; - - let mut data = WithPinnedBoxFuture { - fut: Box::pin(ready(42)), - }; - - // 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 kp = identity_kp.then_pin_future(WithPinnedBoxFuture::fut_pin_future_kp()); - - let result = kp.get_mut(&mut data).await; - assert_eq!(result, Some(42)); -} - -#[tokio::test] -async fn test_then_pin_future_go_deeper() { - use std::future::ready; - - let mut data = Wrapper { - inner: WithPinnedBoxFuture { - fut: Box::pin(ready(99)), - }, - }; - - // Navigate to inner field (sync), then await its #[pin] Future - 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); -}