Rust Developer
Idiomatic Rust at ChainSafe. Full reference: languages/rust/developer.md.
Tooling baselines
CI gates that must pass:
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo build --workspace --all-targets
cargo test --workspace
cargo doc --no-deps
cargo audit
For unsafe-heavy crates: also cargo miri test.
Toolchain
Pin with rust-toolchain.toml:
[toolchain]
channel = "1.79.0"
components = ["rustfmt", "clippy"]
Dependencies
Cargo.lockcommitted for binaries; not for libraries published to crates.io.cargo auditin CI.cargo denyfor license/source/version policy.- Workspace-level dependency declarations to keep versions consistent.
- Feature-gating. Keep
default = []lean — heavy deps (tracing subscribers, DB drivers, codecs, dev utilities) go behind optional features. Put mocks/test vectors behindtest-utilsormockgates; never leak test deps into production builds. Features must be additive (Cargo unifies them across the workspace) — a gate may add behavior, never swap it.
Error handling
use anyhow::{Context, Result};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StoreError {
#[error("key not found: {0}")]
NotFound(String),
#[error("backend failure")]
Backend(#[from] BackendError),
}
pub fn fetch(key: &str) -> Result<Data> {
let raw = read_backend(key).with_context(|| format!("reading {key}"))?;
parse(&raw).context("parsing value")
}
- Library crates: typed
thiserrorerrors. - Binary crates:
anyhow::Result<T>. ?propagation. Match only when transforming.
unwrap and expect
- In tests: fine.
- In production code: require a
// SAFETY: ...comment justifying the invariant. expect("...")overunwrap()when you do use it — message helps debugging.
Async
#[tokio::main]only in binaries.async fnin trait is stable as of 1.75; older crates useasync-trait..awaitpropagates cancellation — hold no critical invariant across an.await.spawnfor async;spawn_blockingfor CPU-bound. Mixing them up starves the runtime.Send + Sync + 'staticbounds on spawned tasks.- Every external
.await(network, DB, cross-subsystem channel read) gets an explicit deadline viatokio::time::timeout. Bounded concurrency caps how many; this caps how long — an.awaitwith no timeout hangs forever.
Unsafe
Every unsafe block:
// SAFETY: ...comment immediately above, explaining soundness.- Justification for choosing
unsafeover safe alternatives. miritest where possible.
Forest's AI_POLICY.md treats unsafe as security-relevant. Expect HARD-FAIL-tier review scrutiny on PRs introducing it.
Testing
- Unit tests in
#[cfg(test)] mod tests { ... }. - Integration tests in
tests/. #[tokio::test]for async tests;flavor = "multi_thread"for parallel.proptestfor parsers, serializers, math, crypto.cargo nextestfor faster CI runs.mirifor unsafe code.
Patterns
- Type-state when the protocol allows it. Encode "must call A before B" in the type system.
- Newtypes for domain types.
pub struct UserId(u64). From/TryFromfor conversions over inherentto_*methods.Derefonly for smart pointers — don't fake inheritance.- API & storage isolation. Keep
#[derive(Serialize, Deserialize)]and storage-schema concerns off domain types. Define serde/wire and DB DTOs at the boundary and map them explicitly to domain models. External edges only — not between internal modules.
Anti-patterns
unwrap()in production paths without// SAFETY: ....Box<dyn Error>returns when you could be specific.async fnthat doesn't.awaitanything.Arc<Mutex<T>>reached for reflexively when a channel would model the problem better.unsafewithout a soundness comment.- Public API leaking tokio types without a feature flag.
Related
- Full reference:
languages/rust/developer.md - Idioms:
languages/rust/idioms.md - Gotchas:
languages/rust/gotchas.md - Sister roles:
chainsafe-rust-architect,chainsafe-rust-reviewer - Forest AI policy: https://github.com/ChainSafe/forest/blob/main/AI_POLICY.md
- Upstream: Effective Rust · The Rust Book