--- paths: - "**/*.rs" --- # Rust Coding Style > This file extends [common/coding-style.md](../common/coding-style.md) with Rust-specific content. ## Formatting - **rustfmt** for enforcement — always run `cargo fmt` before committing - **clippy** for lints — `cargo clippy -- -D warnings` (treat warnings as errors) - 4-space indent (rustfmt default) - Max line width: 100 characters (rustfmt default) ## Immutability Rust variables are immutable by default — embrace this: - Use `let` by default; only use `let mut` when mutation is required - Prefer returning new values over mutating in place - Use `Cow<'_, T>` when a function may or may not need to allocate ```rust use std::borrow::Cow; // GOOD — immutable by default, new value returned fn normalize(input: &str) -> Cow<'_, str> { if input.contains(' ') { Cow::Owned(input.replace(' ', "_")) } else { Cow::Borrowed(input) } } // BAD — unnecessary mutation fn normalize_bad(input: &mut String) { *input = input.replace(' ', "_"); } ``` ## Naming Follow standard Rust conventions: - `snake_case` for functions, methods, variables, modules, crates - `PascalCase` (UpperCamelCase) for types, traits, enums, type parameters - `SCREAMING_SNAKE_CASE` for constants and statics - Lifetimes: short lowercase (`'a`, `'de`) — descriptive names for complex cases (`'input`) ## Ownership and Borrowing - Borrow (`&T`) by default; take ownership only when you need to store or consume - Never clone to satisfy the borrow checker without understanding the root cause - Accept `&str` over `String`, `&[T]` over `Vec` in function parameters - Use `impl Into` for constructors that need to own a `String` ```rust // GOOD — borrows when ownership isn't needed fn word_count(text: &str) -> usize { text.split_whitespace().count() } // GOOD — takes ownership in constructor via Into fn new(name: impl Into) -> Self { Self { name: name.into() } } // BAD — takes String when &str suffices fn word_count_bad(text: String) -> usize { text.split_whitespace().count() } ``` ## Error Handling - Use `Result` and `?` for propagation — never `unwrap()` in production code - **Libraries**: define typed errors with `thiserror` - **Applications**: use `anyhow` for flexible error context - Add context with `.with_context(|| format!("failed to ..."))?` - Reserve `unwrap()` / `expect()` for tests and truly unreachable states ```rust // GOOD — library error with thiserror #[derive(Debug, thiserror::Error)] pub enum ConfigError { #[error("failed to read config: {0}")] Io(#[from] std::io::Error), #[error("invalid config format: {0}")] Parse(String), } // GOOD — application error with anyhow use anyhow::Context; fn load_config(path: &str) -> anyhow::Result { let content = std::fs::read_to_string(path) .with_context(|| format!("failed to read {path}"))?; toml::from_str(&content) .with_context(|| format!("failed to parse {path}")) } ``` ## Iterators Over Loops Prefer iterator chains for transformations; use loops for complex control flow: ```rust // GOOD — declarative and composable let active_emails: Vec<&str> = users.iter() .filter(|u| u.is_active) .map(|u| u.email.as_str()) .collect(); // GOOD — loop for complex logic with early returns for user in &users { if let Some(verified) = verify_email(&user.email)? { send_welcome(&verified)?; } } ``` ## Module Organization Organize by domain, not by type: ```text src/ ├── main.rs ├── lib.rs ├── auth/ # Domain module │ ├── mod.rs │ ├── token.rs │ └── middleware.rs ├── orders/ # Domain module │ ├── mod.rs │ ├── model.rs │ └── service.rs └── db/ # Infrastructure ├── mod.rs └── pool.rs ``` ## Visibility - Default to private; use `pub(crate)` for internal sharing - Only mark `pub` what is part of the crate's public API - Re-export public API from `lib.rs` ## References See skill: `rust-patterns` for comprehensive Rust idioms and patterns.