|
| 1 | +--- |
| 2 | +name: "rust-coding-skill" |
| 3 | +description: "Use whenever editing Rust in PET to write allocation-aware, cross-platform, byte-safe code with behavior-proving tests." |
| 4 | +user-invocable: true |
| 5 | +--- |
| 6 | + |
| 7 | +# PET Rust Coding Skill |
| 8 | + |
| 9 | +Use this alongside `rust-locator-patterns`. Priority order: |
| 10 | + |
| 11 | +1. Readable code with explicit invariants |
| 12 | +2. Correct cross-platform and concurrent behavior |
| 13 | +3. Measured performance improvements without duplicate work |
| 14 | + |
| 15 | +## Path Identity and Caches |
| 16 | + |
| 17 | +Use `Path`/`PathBuf` for paths. Preserve the caller-facing path in reported values, but normalize cache and comparison keys with existing PET helpers such as `norm_case`. |
| 18 | + |
| 19 | +A normalized key does not imply the cached value can expose the first caller's spelling: |
| 20 | + |
| 21 | +```rust |
| 22 | +let key = norm_case(path); |
| 23 | +let mut cached = cache.get(&key)?.clone(); |
| 24 | +cached.prefix = Some(path.to_path_buf()); |
| 25 | +``` |
| 26 | + |
| 27 | +When adding or reviewing a path-keyed cache, check lookup, insert, remove, retain/prune, and state-sync paths. Add Windows coverage using equivalent separators or casing; do not test only the happy-path spelling. |
| 28 | + |
| 29 | +## Byte-Safe Parsing |
| 30 | + |
| 31 | +Never calculate byte offsets from a transformed Unicode string and apply them to the original. Unicode case conversion can change byte length. |
| 32 | + |
| 33 | +For ASCII wire/file markers, use byte-stable ASCII-insensitive matching and checked slicing: |
| 34 | + |
| 35 | +```rust |
| 36 | +let start = find_ascii_case_insensitive(line, "# cmd:")? + "# cmd:".len(); |
| 37 | +let end = find_ascii_case_insensitive(line, " create -")?; |
| 38 | +let value = line.get(start..end)?.trim(); |
| 39 | +``` |
| 40 | + |
| 41 | +Use `to_ascii_lowercase` rather than `to_lowercase` when the format is defined as ASCII. Add a non-ASCII path regression test whenever offsets are derived from textual markers. |
| 42 | + |
| 43 | +## Hot-Path I/O and Allocations |
| 44 | + |
| 45 | +Discovery runs frequently and in parallel. Before adding a cache, prove the repeated work and define invalidation. Within one operation, read immutable metadata once and pass borrowed snapshots through parsers. |
| 46 | + |
| 47 | +- Prefer `&str`/`&[u8]` over cloning content between parsers. |
| 48 | +- Prefer `rfind`/iterator operations over collecting an intermediate `Vec` just to select one item. |
| 49 | +- Avoid `format!` and Unicode case conversion in per-line loops when ASCII matching or direct writes suffice. |
| 50 | +- Do not claim an optimization is complete until every relevant call path is traced, including base/root environments and manager lookup. |
| 51 | +- Do not emit the same warning, telemetry event, or report from both a pre-check and the worker path. |
| 52 | + |
| 53 | +## Error Handling and Locks |
| 54 | + |
| 55 | +Library code should preserve typed information where repository APIs permit it. Prefer `?`, `let-else`, and `if let` over broad fallbacks. Do not swallow filesystem errors when doing so can make stale cache data look valid. |
| 56 | + |
| 57 | +Use contextual `expect` for poisoned locks in production code, matching the surrounding crate. Keep lock scopes short and never perform filesystem I/O or callbacks while holding a shared-state lock unless the design explicitly requires it. |
| 58 | + |
| 59 | +## Cross-Platform Semantics |
| 60 | + |
| 61 | +- Use `#[cfg(...)]` for platform-only code; `cfg!` does not prevent compilation. |
| 62 | +- Avoid `canonicalize` for Windows junction identity; use PET path helpers. |
| 63 | +- Treat both `/` and `\` as separators when parsing user patterns, but only classify `**` as recursive when it is a complete path segment. `foo**bar` is not a recursive segment. |
| 64 | +- Preserve original user-facing paths after normalized comparisons. |
| 65 | + |
| 66 | +## Tests Must Prove the Change |
| 67 | + |
| 68 | +Tests should demonstrate the behavior or performance invariant, not merely execute new lines. |
| 69 | + |
| 70 | +For optimizations, instrument the dependency boundary and assert the operation count: |
| 71 | + |
| 72 | +```rust |
| 73 | +let reads = Cell::new(0); |
| 74 | +parse_with_reader(path, |_| { |
| 75 | + reads.set(reads.get() + 1); |
| 76 | + Some(history.clone()) |
| 77 | +}); |
| 78 | +assert_eq!(reads.get(), 1); |
| 79 | +``` |
| 80 | + |
| 81 | +For parser helpers, include malformed input, non-ASCII surrounding data, and case variations. For diagnostics, test pattern classification and expansion filtering separately. Keep temp paths unique with `tempfile` or process/counter-based names. |
| 82 | + |
| 83 | +Before every Rust commit, run the targeted tests plus `scripts/rust-precommit.ps1` (or `.sh`). Do not suppress Clippy warnings to land a change. |
0 commit comments