Rust coding best practices for idiomatic, efficient, and maintainable code. Use when writing Rust code, reviewing code, or learning Rust patterns.
Install
npx skillscat add thrashr888/thrashr888-agent-kit/rust-best-practices Install via the SkillsCat registry.
SKILL.md
Rust Best Practices
Guidelines for writing idiomatic, efficient, and maintainable Rust code.
Core Principles
- Leverage the type system - Make invalid states unrepresentable
- Prefer compile-time checks - Catch errors before runtime
- Be explicit about ownership - Don't fight the borrow checker
- Write code that passes fmt/clippy first - Not after fixing
Error Handling
Application Code: Use anyhow
use anyhow::{Context, Result, bail};
fn load_config(path: &Path) -> Result<Config> {
let contents = std::fs::read_to_string(path)
.context("Failed to read config file")?;
let config: Config = toml::from_str(&contents)
.context("Failed to parse config")?;
if config.port == 0 {
bail!("Port cannot be 0");
}
Ok(config)
}Library Code: Use thiserror
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Failed to read config: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse config: {0}")]
Parse(#[from] toml::de::Error),
#[error("Invalid configuration: {message}")]
Invalid { message: String },
}Never Use .unwrap() in Library Code
// BAD
let value = map.get("key").unwrap();
// GOOD
let value = map.get("key").ok_or_else(|| Error::MissingKey("key"))?;
// GOOD (when None is truly impossible)
let value = map.get("key").expect("key always present after init");Ownership & Borrowing
Prefer Borrowing Over Cloning
// BAD - unnecessary clone
fn process(data: String) { ... }
process(my_string.clone());
// GOOD - borrow when possible
fn process(data: &str) { ... }
process(&my_string);Use Cow for Flexible Ownership
use std::borrow::Cow;
fn process(data: Cow<'_, str>) -> Cow<'_, str> {
if data.contains("bad") {
Cow::Owned(data.replace("bad", "good"))
} else {
data // No allocation if unchanged
}
}Return Owned Data from Constructors
// GOOD - clear ownership
impl User {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}API Design
Builder Pattern for Complex Configuration
#[derive(Default)]
pub struct ServerBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<Duration>,
}
impl ServerBuilder {
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn build(self) -> Result<Server, ConfigError> {
Ok(Server {
host: self.host.unwrap_or_else(|| "localhost".into()),
port: self.port.ok_or(ConfigError::MissingPort)?,
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
})
}
}Newtype Pattern for Type Safety
// BAD - easy to mix up
fn transfer(from: i64, to: i64, amount: i64) { ... }
// GOOD - compile-time safety
pub struct AccountId(i64);
pub struct Amount(i64);
fn transfer(from: AccountId, to: AccountId, amount: Amount) { ... }Use #[must_use] for Important Returns
#[must_use]
pub fn validate(&self) -> Result<(), ValidationError> {
// ...
}Collections & Iterators
Prefer Iterators Over Loops
// BAD
let mut results = Vec::new();
for item in items {
if item.is_valid() {
results.push(item.transform());
}
}
// GOOD
let results: Vec<_> = items
.into_iter()
.filter(|item| item.is_valid())
.map(|item| item.transform())
.collect();Use collect() Type Inference
// Collect into Vec
let vec: Vec<_> = iter.collect();
// Collect into HashMap
let map: HashMap<_, _> = iter.collect();
// Collect Results
let results: Result<Vec<_>, _> = iter.collect();Async Patterns
Use tokio for Async Runtime
#[tokio::main]
async fn main() -> Result<()> {
let result = fetch_data().await?;
Ok(())
}Avoid Blocking in Async Code
// BAD - blocks the runtime
async fn bad() {
std::thread::sleep(Duration::from_secs(1));
}
// GOOD - async sleep
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
}
// GOOD - spawn blocking for CPU-intensive work
async fn compute() -> i32 {
tokio::task::spawn_blocking(|| expensive_computation()).await.unwrap()
}Testing
Unit Tests in Same File
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_edge_case() {
assert!(validate("").is_err());
}
}Integration Tests in tests/
// tests/integration_test.rs
use my_crate::public_api;
#[test]
fn test_full_workflow() {
let result = public_api::process("input");
assert!(result.is_ok());
}Use assert! Macros Effectively
assert!(condition);
assert_eq!(left, right);
assert_ne!(left, right);
assert!(result.is_ok());
assert!(result.is_err());
assert_matches!(value, Pattern::Variant { .. });Performance
Avoid Premature Allocation
// BAD - allocates even if not needed
fn maybe_string() -> String {
String::from("default")
}
// GOOD - return static str when possible
fn maybe_string() -> &'static str {
"default"
}Use Vec::with_capacity for Known Sizes
// BAD - multiple reallocations
let mut vec = Vec::new();
for i in 0..1000 {
vec.push(i);
}
// GOOD - single allocation
let mut vec = Vec::with_capacity(1000);
for i in 0..1000 {
vec.push(i);
}Profile Before Optimizing
cargo build --release
cargo flamegraph # requires cargo-flamegraphModule Organization
Keep Modules Focused
// src/lib.rs
pub mod config;
pub mod client;
pub mod error;
// Re-export public API
pub use config::Config;
pub use client::Client;
pub use error::Error;Use pub(crate) for Internal APIs
// Public to crate, not external users
pub(crate) fn internal_helper() { ... }Documentation
Document Public APIs
/// Creates a new client with the given configuration.
///
/// # Arguments
///
/// * `config` - The client configuration
///
/// # Errors
///
/// Returns an error if the configuration is invalid.
///
/// # Examples
///
/// ```
/// let client = Client::new(Config::default())?;
/// ```
pub fn new(config: Config) -> Result<Self> {
// ...
}Anti-Patterns to Avoid
| Anti-Pattern | Better Approach |
|---|---|
.unwrap() everywhere |
Use ? operator |
clone() to satisfy borrow checker |
Restructure ownership |
String parameters |
Use &str or impl Into<String> |
| Boolean parameters | Use enums |
| Long function bodies | Extract to smaller functions |
| Deep nesting | Use early returns |
| Magic numbers | Use named constants |
Quick Reference
# Quality gates
cargo fmt -- --check && cargo clippy -- -D warnings && cargo test
# Common cargo commands
cargo check # Fast syntax/type check
cargo build # Debug build
cargo build --release # Release build
cargo test # Run tests
cargo doc --open # Generate and view docs
cargo clippy --fix # Auto-fix lint issues