ADR-024
Adopt SOTA Rust patterns: bon Builder, Arc<str>, camino, sealed module, strum, public-api
accepted · 2026-04-16 · L0 L0.5 · cites 5
0ADR-024: Adopt SOTA Rust patterns #
Context #
Multi-agent audit (rust-architect + rust-pro + web-researcher) identified six 2025-2026 SOTA Rust patterns that Nika can adopt cheaply at v0.1 with high forward-compat ROI.
Decision #
1. `bon::Builder` derive as DEFAULT for ≥4-arg structs #
Adopters: rust-analyzer, axum, tokio (evaluating). Type-state safety,
#[non_exhaustive]-compatible, eliminates ..Default::default() tax.
Apply to top 5 first: InferRequest, AgentLoopConfig, ModelCapabilities,
HttpRequest, ScheduleConfig. Inv #19 amended: new() for ≤3-arg structs,
bon::Builder for ≥4-arg.
2. `Arc<str>` for shared catalog identifiers #
ModelId, ProviderId, capability rule keys: migrate from String to
Arc<str>. Half the size, O(1) clone (refcount). Static catalog literals stay
as &'static str (phf keys unchanged).
3. `camino::Utf8PathBuf` for ALL paths in nika-core #
Kills the OsStr → str unwrap question forever. Matches Cargo, jj, oxc, ruff,
uv convention.
4. `mod sealed { pub trait Sealed {} }` as the ONLY sealed pattern #
Per Agent 2 SOTA: line-for-line identical pattern across rust-analyzer, biome,
ruff, bevy, tokio, oxc. Drop the proposed sealed proc-macro crate (3-project
trend, not convergence).
5. `strum::EnumString + IntoStaticStr` for enum↔string bridges #
Specifically for EventKind aggregation in nika-event (22 sub-enums). Used by
oxc (200+ AstKind variants), bevy (Reflect), sqlx (error codes). Q1 still
holds (no proc macros in L0 impl); strum is a dev-dep proc macro, not Nika-
owned L0 code.
6. `cargo public-api` baselines NOW for L0 + L0.5 #
Snapshot every public symbol of every L0/L0.5 crate into
docs/api-baselines/<crate>.txt. Pre-PR hook diffs against baseline. Mechanizes
the #[non_exhaustive] ratchet (vector 18 made enforceable).
Consequences #
- ✅ Type-state builder safety (#1) prevents misconstructed configs at compile time
- ✅
Arc<str>(#2) — half the alloc per catalog lookup - ✅
camino(#3) — kills a class of unwrap bugs - ✅ Plain
mod sealed(#4) — minimal complexity, industry standard - ✅
strum(#5) — eliminates handwrittenas_str()/parse()boilerplate - ✅
public-api(#6) — automated #[non_exhaustive] ratchet - ❌ +4 workspace deps:
bon,camino,strum,cargo-public-api(cargo subcommand, not a runtime dep) - ❌ ~8 commits of mechanical migration work across Phase H
Migration order #
cargo public-apisetup + baselines (no code changes — PR-blocking infrastructure)mod sealedstandardization (already mostly there)camino::Utf8PathBufmigration (mechanical find/replace)Arc<str>for ModelId/ProviderId (touches catalog; coordinated with ADR-022 split)strumfor EventKind in nika-event (during nika-event build)bon::Builderfor top 5 config structs (last — depends on stable APIs)
Alternatives considered #
- `derive_builder` instead of
bon— bon's #[non_exhaustive] support is the tipping point - `SmolStr` / `CompactString` instead of
Arc<str>— defer until cargo-bench justifies (small-string optimization is hot-path-specific) - `std::path::PathBuf` instead of
camino::Utf8PathBuf— keeps OsStr→str unwrap question - Custom `as_str()` on each enum instead of
strum— boilerplate at scale
read at v0.107.0 · the decision record ships with the engine