rust-guide

Applies to: Rust 2021 edition+, Systems Programming, CLIs, WebAssembly, APIs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rust-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-rust-guide

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 every unsafe block in writing

  • Clippy Compliance: All code passes cargo clippy -- -D warnings with 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.lock for binaries; omit for libraries

  • Run cargo update weekly for security patches

Code Style

  • Run cargo fmt before every commit (no exceptions)

  • Run cargo clippy -- -D warnings before every commit

  • Follow Rust API Guidelines

  • snake_case for functions, variables, modules, crate names

  • PascalCase for types, traits, enums, type parameters

  • SCREAMING_SNAKE_CASE for constants and statics

  • Prefer exhaustive match over if let chains

  • 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 thiserror for library error types, anyhow for application code

  • Never use String as an error type (create enums or use thiserror )

  • Use ? operator for propagation; avoid manual match on Result when ? suffices

  • unwrap() is forbidden outside tests and examples

  • expect("reason") is allowed only with a comment explaining the invariant

  • Use #[must_use] on functions returning Result or 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 'static bounds unless truly needed (e.g., thread spawning, lazy statics)

Concurrency

  • Use tokio as the async runtime (unless project requires async-std )

  • All async operations must have timeouts via tokio::time::timeout

  • Use Arc<T> for shared ownership across threads; never Rc<T> in async code

  • Prefer tokio::sync::Mutex over std::sync::Mutex in async contexts

  • Use channels (mpsc , oneshot , broadcast ) over shared mutable state

  • Spawn tasks with tokio::spawn ; propagate errors via JoinHandle

  • 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.rs should be thin: parse CLI args, build config, call lib.rs entry

  • Put all business logic in lib.rs modules (testable without binary)

  • Use workspaces for projects with 3+ crates

  • error.rs at 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(&#x26;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&#x3C;(), Box&#x3C;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 tests in 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 tarpaulin or cargo llvm-cov for 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-version in 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

  • The Rust Programming Language

  • Rust by Example

  • Rust API Guidelines

  • The Async Book

  • Rustonomicon (Unsafe Rust)

  • Clippy Lint Reference

  • Error Handling in Rust (blog)

  • cargo-deny

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

python-guide

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-guide

No summary provided by upstream source.

Repository SourceNeeds Review