|
| 1 | +# radkit Rust Engineering Playbook |
| 2 | + |
| 3 | +This playbook documents the expectations for production-grade Rust within the radkit agent SDK. It covers design principles, ownership discipline, safety, validation, and the workflow needed to ship confidently across native and WASM targets. |
| 4 | + |
| 5 | +## Engineering Values |
| 6 | +- Prefer clarity over cleverness. Readability, predictability, and explicit invariants take precedence over micro-optimizations. |
| 7 | +- Embrace soundness. Design APIs so the compiler can enforce correctness with types, lifetimes, and traits. |
| 8 | +- Minimize hidden state. Favor pure functions, explicit data flow, and narrow interfaces between modules. |
| 9 | +- Guard portability. Ensure code paths work across Linux, macOS, Windows, and WASM unless a platform-specific feature is explicitly required. |
| 10 | + |
| 11 | +## Project Configuration |
| 12 | +- Declare the current `rust-version` (MSRV) in `Cargo.toml` and update it intentionally. All CI jobs must build and test against this MSRV. |
| 13 | +- Use Rust 2021 edition unless a later stable edition is required; keep `rust-toolchain.toml` in sync with the workspace toolchain. |
| 14 | +- At the crate root enable the strongest baseline lints we can sustain, e.g. |
| 15 | + ```rust |
| 16 | + #![deny(unsafe_code, unreachable_patterns, unused_must_use)] |
| 17 | + #![warn(clippy::all, clippy::pedantic, clippy::nursery)] |
| 18 | + ``` |
| 19 | + Document any relaxed lint so reviewers know why it is needed. |
| 20 | +- Keep `lib.rs`/`main.rs` dependency graphs under control by isolating optional functionality in feature-gated modules. |
| 21 | + |
| 22 | +## API and Architecture Design |
| 23 | +- Model domain concepts explicitly. Use `struct` and `enum` types with `#[non_exhaustive]` only when future-proofing is intentional. |
| 24 | +- Prefer `trait`-driven design over type erasure. Implement conversion traits (`From`, `TryFrom`, `Into`) for ergonomic interop. |
| 25 | +- Hide implementation detail behind `pub(crate)` modules. Expose the smallest viable public surface and document invariants via `///` comments. |
| 26 | +- Keep constructors and builders validating their inputs; use smart constructors to guarantee invariants after instantiation. |
| 27 | +- Separate synchronous, asynchronous, and WASM-specific APIs with feature flags or module boundaries to avoid mixing concerns. |
| 28 | + |
| 29 | +## Ownership and Borrowing Discipline |
| 30 | +- Default to borrowing (`&T`, `&mut T`) and slices (`&[T]`) in APIs; take ownership only when necessary. Return `&str`/`&[u8]` instead of allocating `String`/`Vec` when data lives long enough. |
| 31 | +- Avoid `clone()` on hot paths. Reach for `Cow<'_, T>`, iterators, or reference-counted pointers (`Arc`, `Rc`) when sharing data. |
| 32 | +- Design explicit lifetimes when returning references from structs. Prefer `Arc` + `Weak` for shared ownership with drop ordering requirements. |
| 33 | +- Work with interior mutability (`Mutex`, `RwLock`, `parking_lot`, `RefCell`) only when borrowing rules cannot model the invariants. Document why interior mutability is required. |
| 34 | +- Validate invariants inside `Drop` implementations and avoid surprising side effects; do not block or panic inside `Drop` paths. |
| 35 | + |
| 36 | +## Async and Concurrency |
| 37 | +- Restrict asynchronous APIs to types that are `Send + Sync` unless the lack of thread safety is explicitly documented. |
| 38 | +- Never block the runtime. Route CPU intensive or blocking I/O work through `spawn_blocking` or dedicated worker threads. |
| 39 | +- Propagate cancellation with `futures::select!`, `tokio::select!`, or cooperative checks so async tasks tear down quickly. |
| 40 | +- Use `Arc` + `RwLock`/`Mutex` sparingly; prefer message passing (`mpsc`, `broadcast`, `watch`) or atomics for shared state. |
| 41 | +- Validate cross-task ordering with tools like `loom` for tricky concurrency primitives; document assumptions about ordering and visibility. |
| 42 | +- Ensure WASM code paths stay single-threaded: guard multi-threaded constructs with `#[cfg(not(target_family = "wasm"))]`. |
| 43 | + |
| 44 | +## Error Handling |
| 45 | +- Surface typed errors from library boundaries using `thiserror` or manual enums. Provide context with `anyhow::Context` or `eyre::WrapErr` inside leaf functions, but do not leak `anyhow::Error` across crate boundaries. |
| 46 | +- Map external errors into our domain-specific variants early so upstream code can match on them predictably. |
| 47 | +- Avoid panics except for programmer bugs (`debug_assert!`) or irrecoverable invariants. Audit `unwrap`/`expect` in PRs; justify any remaining usage in comments. |
| 48 | +- Use `Result<T, E>` even for internal helpers if failure is possible. Prefer returning `Option<T>` only when absence is the only failure mode. |
| 49 | + |
| 50 | +## Formatting, Lints, and Static Analysis |
| 51 | +- Run `cargo fmt --all` before every commit; CI rejects formatting drift. |
| 52 | +- Keep `cargo clippy --workspace --all-targets --all-features -D warnings` green. Document every `#[allow]` with a reason and a follow-up issue if needed. |
| 53 | +- Periodically run `cargo fix --allow-dirty` to adopt new idioms introduced by compiler upgrades, but always review the diff manually. |
| 54 | +- Use `cargo udeps` to remove unused dependencies and `cargo deny` or `cargo audit` to flag vulnerable or unlicensed crates as part of release pre-checks. |
| 55 | + |
| 56 | +## Testing and Validation |
| 57 | +- Require unit tests for every public function, trait impl, state machine, and error path. Favor table-driven tests for deterministic coverage. |
| 58 | +- Exercise async logic with `tokio::test` (native) and `wasm-bindgen-test` (WASM). For concurrent code, include regression tests that drive cancellation and ordering edge cases. |
| 59 | +- Add property or fuzz tests (`proptest`, `arbitrary`, `cargo fuzz`) for parsing, serialization, and protocol logic. |
| 60 | +- Keep integration tests hermetic by mocking filesystem, network, and time; use contract tests when integrating with real services is unavoidable. |
| 61 | +- Track branch coverage with `cargo llvm-cov` (native) and `wasm-bindgen-test --coverage` (WASM). Treat meaningful coverage gaps as blockers before merge. |
| 62 | + |
| 63 | +## Observability and Diagnostics |
| 64 | +- Emit structured logs through `tracing` with consistent target names. Use `#[tracing::instrument]` on async entry points to preserve spans across awaits. |
| 65 | +- Attach error context (`tracing::error!`, `tracing::warn!`) with actionable messages; avoid logging secrets or personal data. |
| 66 | +- Collect metrics via the workspace metrics facade (histograms for latency, counters for events). Update dashboards or alerts when behavior changes. |
| 67 | +- Provide troubleshooting guides in module-level docs for complex components; include sample traces or metric expectations where helpful. |
| 68 | + |
| 69 | +## Performance and Memory |
| 70 | +- Favor iterators, slices, and views over temporary allocations. Reuse buffers with `SmallVec`, `Bytes`, or pooling strategies when profiling proves the need. |
| 71 | +- Benchmark critical paths with `cargo bench` or `criterion` before landing optimizations. Capture flamegraphs (`cargo flamegraph`) to validate wins. |
| 72 | +- Keep hot data structures in cache-friendly shapes (avoid large enums with padding; consider `#[repr(C)]` only with clear justification). |
| 73 | +- Audit cloning, locking, and allocations during code review; measure before optimizing and document trade-offs. |
| 74 | + |
| 75 | +## Dependency Management |
| 76 | +- Default to `std`, `futures`, and `tokio` (or WASM executor) primitives. Introduce new crates only with an ADR or design note describing rationale, maintenance plan, and licensing. |
| 77 | +- Prefer minimal feature sets. Disable default features for crates we import and enable exactly what we need. |
| 78 | +- Regularly run `cargo update -p <crate>` alongside release branches; capture upgrade notes in `CHANGELOG.md`. |
| 79 | +- Keep native-only dependencies behind `cfg` gates or feature flags. Provide WASM-friendly alternatives to avoid pulling incompatible crates into the WASM artifact. |
| 80 | + |
| 81 | +## Runtime Compatibility Helpers |
| 82 | +- Route native/WASM behavioral differences through `radkit/src/compat.rs` so calling sites stay portable; prefer using the helpers (`MaybeSend`, `compat::spawn`, `compat::time`, etc.) instead of ad-hoc `#[cfg]` blocks in business logic. |
| 83 | +- Extend `radkit/src/compat.rs` when new cross-target abstractions are needed, mirroring APIs on both sides and documenting any WASM limitations in the module so downstream crates can plan around them. |
| 84 | +- Gate platform-specific dependencies inside `compat.rs` and re-export neutral aliases; avoid leaking executor-specific types (e.g., `tokio::Mutex`) outside the module. |
| 85 | +- Exercise both native and WASM code paths whenever `compat.rs` changes by running native tests plus the relevant WASM smoke/regression tests to catch divergences early. |
| 86 | + |
| 87 | +## Build Targets |
| 88 | +- Maintain dual build targets: |
| 89 | + - Native: `cargo build --workspace --all-features` for development, load testing, and services. |
| 90 | + - WASM: `cargo build --target wasm32-wasi --release` for deployment. |
| 91 | +- Guard native-only build steps in `build.rs` with `cfg!(not(target_arch = "wasm32"))` and fail fast with helpful messages when the target is unsupported. |
| 92 | +- Run smoke tests for WASM artifacts via `wasmtime`, `wasmer`, or the host embedding to validate ABI changes. |
| 93 | + |
| 94 | +## Release and Packaging |
| 95 | +- Follow semantic versioning. Breaking API changes require a major version bump and migration guide under `docs/`. |
| 96 | +- Automate tagging with `cargo release` or an equivalent tool; include generated documentation and release notes in the pipeline output. |
| 97 | +- Ship reproducible builds: pin toolchain versions, vetted dependencies, and ensure `cargo package` is clean (`cargo package --allow-dirty` is prohibited). |
| 98 | +- Archive WASM artifacts with their interface metadata and checksum for downstream verification. |
| 99 | + |
| 100 | +## Code Review and Collaboration |
| 101 | +- Open design discussions (RFCs or ADRs) before implementing cross-cutting changes or introducing new dependencies. |
| 102 | +- Keep PRs focused and under ~400 lines where possible. Include a narrative explaining the change, validation performed, and follow-up tasks. |
| 103 | +- Reviewers check for ownership leaks, blocking operations, error propagation, and missing tests before approving. |
| 104 | +- Merge only with green CI covering formatting, clippy, unit/integration tests, WASM tests, and audit checks. |
| 105 | + |
| 106 | +## Workflow Checklist |
| 107 | +1. Update or add targeted tests before implementing behavior changes. |
| 108 | +2. Run `cargo fmt`, `cargo clippy --all-targets --all-features -D warnings`, and native tests (`cargo test --workspace`). |
| 109 | +3. Run WASM tests or smoke checks (`wasm-pack test --node`, `wasmtime <artifact>.wasm`, or equivalent) when code affects shared logic. |
| 110 | +4. Inspect coverage via `cargo llvm-cov` (native) and `wasm-bindgen-test --coverage` for relevant crates; backfill missing cases. |
| 111 | +5. Verify documentation builds cleanly with `cargo doc --no-deps` and keep public API changes documented. |
| 112 | +6. Capture release notes or changelog entries for externally visible changes. |
| 113 | +7. Land the change only after peer review and green CI across all required jobs. |
| 114 | + |
| 115 | +Adhering to these practices keeps the radkit SDK reliable, portable, and maintainable while delivering a disciplined developer experience. |
0 commit comments