Rust Architect
Use this when designing Rust systems at ChainSafe — Forest-shaped work or any Rust crate/library/binary. Full reference: languages/rust/architect.md.
Key Rust-specific decisions
Workspace layout
ChainSafe Rust projects use Cargo workspaces (Forest is the canonical example):
- Root
Cargo.tomldeclares workspace members. - Crates split by concern (network, storage, binary, protocol modules).
- A
bin/crate for the executable; alib/crate for the consumable API. - Workspace-level dependency hoisting where possible.
Error model
thiserrorfor library crates. Typed errors callers can match via#[derive(thiserror::Error)].anyhowfor binary crates. Pass-through errors with.context(...)for ergonomics.- No mixing within a single crate without reason.
?for propagation. Nomatcharms on trivial pass-through.
Async runtime
- Pick one. Tokio for most ChainSafe work (Forest included). Don't mix tokio and async-std in the same workspace.
#[tokio::main]only in the binary crate. Library crates stay runtime-agnostic where possible.- Bounded concurrency:
FuturesUnorderedwith limits,tokio::sync::Semaphore,buffer_unorderedon streams. Unbounded futures-spawn is a leak. Send + Sync + 'staticbounds — design types so they meet whattokio::spawnrequires.- Bounded Duration: Every potentially blocking external
.awaitboundary (network I/O, database interaction, channel reads across subsystems) must have a strict timeout boundary. Combine bounded concurrency (caps how many) with bounded duration (caps how long) to prevent task pool exhaustion.
Concurrency primitives
Arc<Mutex<T>>for shared mutable state across tasks.tokio::sync::Mutexwhen locks span.await;std::sync::Mutexwhen they don't.- Channels:
mpscfor many-to-one,broadcastfor one-to-many,oneshotfor single-response. - No
std::thread::spawnin async code. Usetokio::task::spawnortokio::task::spawn_blocking.
Public API
pub(crate)is the default.pubis a versioning commitment.#[non_exhaustive]on enums and structs that may grow.- Builder pattern for complex constructors.
- No leaking tokio types in library APIs unless feature-gated.
- API & storage isolation. Keep internal domain types separate from external transport layers. Define serde/wire and database DTOs at the boundary and map them explicitly to domain models; don't hang
#[derive(Serialize, Deserialize)]or storage-schema concerns directly on domain types. External edges only — not between internal modules.
Feature-gating and compilation boundaries
- Lean defaults. Keep
default = []as minimal as possible. Heavy dependencies (tracing subscribers, database drivers, serialization codecs, dev utilities) live behind optional cargo features, not in the default build. - Environment isolation. Abstract environment-specific logic — mocks, test vectors, alternative networking layers — behind descriptive gates like
test-utilsormock. Never leak testing dependencies into production builds. - Conditional compilation. Apply
#[cfg(feature = "...")]intentionally on modules and public entry points so that turning a feature off fully removes its artifacts and upstream dependencies from the compilation graph. - Features are additive. Cargo unifies feature sets across the workspace, so a feature may only add behavior — never remove or swap it. A
mockgate that replaces real behavior breaks the moment two crates in one build enable different sets; design gates to layer, not to toggle.
Unsafe
- Every
unsafeblock needs a// SAFETY: ...comment with the soundness argument. - Justification beyond "performance."
miritesting path when possible.- Minimized — wrap the smallest possible code in
unsafe { }.
ADR template for Rust work
- Public surface —
pubvspub(crate)vs private. Justify. - Error type —
thiserrororanyhow. Justify. - Async commitments — which runtime, where required, how the API is shaped.
- Unsafe — if any, the soundness argument and safety invariants.
- Invariants impacted — deep links into
.invariants.
Anti-patterns at design time
unwrap()in non-test code without// SAFETY: ....Box<dyn Error>as a function's return type when you could be specific.Stringeverywhere when&strwould do.- Tokio types leaked through public APIs of library crates without feature flags.
unsafewithout a soundness comment.
Forest-specific
Forest carries AI_POLICY.md which sets the security-critical posture for AI-assisted work in a Filecoin client. Architectural decisions on Forest follow that policy — review-time the Rust reviewer skill will enforce.
Related
- Full reference:
languages/rust/architect.md - Sister roles:
chainsafe-rust-developer,chainsafe-rust-reviewer - Framework:
invariants/invariants-framework.md - Workflow:
chainsafe-research-plan-implement - Upstream: Effective Rust · The Rust Book