rust-guide
SKILL.md
Rust Guide
Applies to: Rust 2021 edition+, Systems Programming, CLIs, WebAssembly, APIs
Core Principles
- Ownership Clarity: Every value has one owner; borrowing is explicit and intentional
- Result Over Panic: Return
Result<T, E>for all fallible operations;panic!is a bug - Zero-Cost Abstractions: Use iterators, generics, and traits without runtime overhead
- Minimal Unsafe: Default to
#[forbid(unsafe_code)]; justify everyunsafeblock in writing - Clippy Compliance: All code passes
cargo clippy -- -D warningswith zero exceptions
Guardrails
Edition & Toolchain
- Use Rust 2021 edition (
edition = "2021"in Cargo.toml) - Set
rust-version(MSRV) in Cargo.toml for all published crates - Use stable toolchain unless a nightly feature is explicitly justified
- Pin dependency versions with
~for compatible updates in libraries - Commit
Cargo.lockfor binaries; omit for libraries - Run
cargo updateweekly for security patches
Code Style
- Run
cargo fmtbefore every commit (no exceptions) - Run
cargo clippy -- -D warningsbefore every commit - Follow Rust API Guidelines
snake_casefor functions, variables, modules, crate namesPascalCasefor types, traits, enums, type parametersSCREAMING_SNAKE_CASEfor constants and statics- Prefer exhaustive
matchoverif letchains - No
use super::*in non-test modules (explicit imports only) - Derive order:
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize
Error Handling
- Return
Result<T, E>for all operations that can fail - Use
thiserrorfor library error types,anyhowfor application code - Never use
Stringas an error type (create enums or usethiserror) - Use
?operator for propagation; avoid manualmatchonResultwhen?suffices unwrap()is forbidden outside tests and examplesexpect("reason")is allowed only with a comment explaining the invariant- Use
#[must_use]on functions returningResultor important values
Lifetimes
- Let the compiler infer lifetimes whenever possible (elision rules)
- Add explicit annotations only when the compiler requires them
- Prefer owned types in structs; use references only when zero-copy is measured to matter
- Use
Cow<'a, str>when a function may or may not allocate - Avoid
'staticbounds unless truly needed (e.g., thread spawning, lazy statics)
Concurrency
- Use
tokioas the async runtime (unless project requiresasync-std) - All async operations must have timeouts via
tokio::time::timeout - Use
Arc<T>for shared ownership across threads; neverRc<T>in async code - Prefer
tokio::sync::Mutexoverstd::sync::Mutexin async contexts - Use channels (
mpsc,oneshot,broadcast) over shared mutable state - Spawn tasks with
tokio::spawn; propagate errors viaJoinHandle - Every
select!branch must handle cancellation
Project Structure
Binary Crate
myproject/
├── Cargo.toml
├── Cargo.lock
├── src/
│ ├── main.rs # Entry point, minimal (parse args, call run)
│ ├── lib.rs # Public API and module declarations
│ ├── config.rs # Configuration loading
│ ├── error.rs # Custom error types
│ ├── domain/
│ │ ├── mod.rs
│ │ └── user.rs
│ └── infra/
│ ├── mod.rs
│ ├── db.rs
│ └── http.rs
├── tests/ # Integration tests
│ └── api_test.rs
├── benches/ # Benchmarks (criterion)
│ └── throughput.rs
└── examples/
└── basic_usage.rs
Cargo Workspace
workspace/
├── Cargo.toml # [workspace] members
├── crates/
│ ├── core/ # Domain logic (no I/O)
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ ├── api/ # HTTP layer
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ └── cli/ # Binary entry point
│ ├── Cargo.toml
│ └── src/main.rs
└── tests/ # Workspace-level integration tests
main.rsshould be thin: parse CLI args, build config, calllib.rsentry- Put all business logic in
lib.rsmodules (testable without binary) - Use workspaces for projects with 3+ crates
error.rsat crate root defines the crate-level error enum- No global mutable state; use dependency injection via function arguments or structs
Error Handling Patterns
Library Errors (thiserror)
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StorageError {
#[error("record {id} not found in {table}")]
NotFound { table: &'static str, id: String },
#[error("duplicate key: {0}")]
Conflict(String),
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}
Application Errors (anyhow)
use anyhow::{bail, Context, Result};
fn load_config(path: &str) -> Result<Config> {
let raw = std::fs::read_to_string(path)
.with_context(|| format!("reading config from {path}"))?;
let config: Config = toml::from_str(&raw)
.context("parsing TOML config")?;
if config.port == 0 {
bail!("port must be non-zero");
}
Ok(config)
}
Conversion With the ? Operator
// Define From impls via thiserror's #[from], then just use ?
fn create_user(db: &Pool, input: &NewUser) -> Result<User, AppError> {
validate_email(&input.email)?; // ValidationError -> AppError
let user = db.insert(input)?; // sqlx::Error -> AppError
Ok(user)
}
Testing
Unit Tests (in-module)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid_email_succeeds() {
let result = Email::parse("user@example.com");
assert!(result.is_ok());
}
#[test]
fn parse_invalid_email_returns_error() {
let err = Email::parse("not-an-email").unwrap_err();
assert!(matches!(err, ValidationError::InvalidEmail(_)));
}
#[test]
fn amount_cannot_be_negative() -> Result<(), Box<dyn std::error::Error>> {
let result = Amount::new(-5);
assert!(result.is_err());
Ok(())
}
}
Integration Tests (tests/ directory)
// tests/api_test.rs
use myproject::app;
#[tokio::test]
async fn health_endpoint_returns_ok() {
let app = app::build_test_app().await;
let resp = app.get("/health").await;
assert_eq!(resp.status(), 200);
}
Doc Tests
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use mycrate::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Property-Based Testing (proptest)
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrip_serialization(input in "[a-zA-Z0-9]{1,100}") {
let encoded = encode(&input);
let decoded = decode(&encoded)?;
prop_assert_eq!(input, decoded);
}
}
Testing Standards
- Test names describe behavior:
fn rejected_order_cannot_be_shipped() - Unit tests live in
#[cfg(test)] mod testsin the same file - Integration tests go in
tests/directory - Use
#[should_panic(expected = "...")]for panic-testing (rare) - Use
assert!(matches!(...))for enum variant assertions - Coverage target: >80% for libraries, >60% for applications
- Use
cargo tarpaulinorcargo llvm-covfor coverage - Async tests use
#[tokio::test]
Tooling
Essential Commands
cargo fmt # Format (non-negotiable)
cargo clippy -- -D warnings # Lint with deny
cargo test # All tests
cargo test --all-features # Test all feature combinations
cargo check # Fast type-check (no codegen)
cargo doc --open # Generate and view docs
cargo audit # Check for vulnerable deps
cargo deny check # License + advisory check
cargo bench # Run benchmarks (criterion)
Cargo.toml Essentials
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[dependencies]
serde = { version = "1", features = ["derive"] }
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tracing = "0.1"
[dev-dependencies]
proptest = "1"
tokio = { version = "1", features = ["test-util"] }
[profile.release]
lto = true
codegen-units = 1
strip = true
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
unwrap_used = "deny"
expect_used = "warn"
Rustfmt Configuration
# rustfmt.toml
edition = "2021"
max_width = 100
use_field_init_shorthand = true
MSRV Policy
- Set
rust-versionin Cargo.toml for every published crate - Test MSRV in CI:
cargo +1.75.0 check - Bump MSRV only when a dependency or language feature requires it
- Document MSRV bumps in changelog
Advanced Topics
For detailed patterns and examples, see:
- references/patterns.md -- Async server, builder, newtype, state machine patterns
- references/pitfalls.md -- Common ownership mistakes, lifetime traps, async gotchas
- references/security.md -- Unsafe auditing, dependency vetting, secret handling
External References
Weekly Installs
10
Repository
ar4mirez/samuelGitHub Stars
3
First Seen
Feb 20, 2026
Security Audits
Installed on
opencode10
gemini-cli10
github-copilot10
codex10
amp10
kimi-cli10