Rust Best Practices
Ownership and Borrowing
- Follow the ownership model: each value has a single owner, dropped when the owner goes out of scope
- Use
&Tfor borrowed references; prefer&selfover&mut selfin method signatures - Apply
Cloneonly when necessary -- prefer references orCopyfor small types - Use
Rc<T>for shared ownership in single-threaded contexts,Arc<T>for multi-threaded - Avoid
RefCell<T>in performance-critical paths; restructure data to satisfy the borrow checker statically
Error Handling
- Use
Result<T, E>as the standard return type for fallible operations; never panic in library code - Define domain errors with
thiserrorfor library crates:#[derive(Error)]with descriptive variants - Use
anyhowfor application-level error propagation with context:.context("failed to open config")? - Create a unified
AppErrortype at application boundaries withFromtrait impls for conversion - Use
unwrap()andexpect()only in tests or cases where failure is genuinely impossible
Async with Tokio
- Use
tokioas the async runtime; annotatemainwith#[tokio::main]and select the appropriate flavor (flavor = "current_thread"for lightweight,multi_threadfor CPU-bound) - Prefer
tokio::spawnfor concurrent tasks; useJoinHandleto await results - Use
tokio::sync::Mutex(notstd::sync::Mutex) across.awaitpoints to avoid holding OS locks across yields - Apply
tokio::select!for racing futures and handling cancellation - Use channels (
mpsc,broadcast,watch) for inter-task communication over shared mutable state
Trait Design
- Design traits for behavior, not data; keep trait methods focused and composable
- Use associated types when the return type varies by implementor:
type Output - Apply trait bounds at the function level, not the struct level, to maximize flexibility
- Implement
From/Intofor type conversions rather than ad-hoc methods - Use
dyn Traitfor dynamic dispatch only when static dispatch with generics is impractical
Cargo and Project Structure
- Use cargo workspaces for multi-crate projects: define
[workspace]in the rootCargo.toml - Keep binary, library, and test targets separated under
src/bin/,src/lib.rs, andtests/ - Pin dependency versions with
Cargo.lockfor binaries; omit it for published libraries - Use
#[cfg(test)]modules for unit tests within source files andtests/directory for integration tests - Profile for release builds: set
opt-level,lto = true, andcodegen-units = 1inCargo.tomlfor maximum performance
WebAssembly Targets
- Target
wasm32-unknown-unknownfor browser WASM; usewasm-bindgenfor JS interop - Use
wasm-packfor building and publishing WASM packages - Avoid
std::fs,std::net, and blocking I/O in WASM targets -- use browser APIs viaweb-sys - Keep WASM modules small: enable
wee_allocas the global allocator and tree-shake with LTO - Test WASM logic in native Rust first, then verify in browser with
wasm-pack test --chrome
Source: calcosmic/Aether — distributed by TomeVault.