Write reliable code, unit tests, integration tests, documentation, and idiomatic Rust patterns.
#[test], #[cfg(test)])Unit tests verify individual components in isolation. They live alongside your code inside a #[cfg(test)] module and are compiled only when running cargo test.
// Basic unit test structure
fn add(a: i32, b: i32) -> i32 {
a + b
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
assert_eq!(add(-1, 1), 0);
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10, 2).unwrap(), 5);
assert_eq!(divide(7, 2).unwrap(), 3);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(5, 0).is_err());
match divide(10, 0) {
Err(msg) => assert_eq!(msg, "Division by zero"),
Ok(_) => panic!("Should have errored"),
}
}
#[test]
#[should_panic(expected = "attempt to subtract with overflow")]
fn test_overflow() {
let _ = i32::MIN - 1;
}
}// Testing with custom messages and assertions
struct Calculator;
impl Calculator {
fn factorial(n: u32) -> u32 {
match n {
0 | 1 => 1,
n => n * Self::factorial(n - 1),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_factorial() {
assert_eq!(Calculator::factorial(0), 1);
assert_eq!(Calculator::factorial(1), 1);
assert_eq!(Calculator::factorial(5), 120,
"5! should equal 120");
assert_eq!(Calculator::factorial(10), 3628800);
}
#[test]
fn test_factorial_properties() {
let n = 6;
let result = Calculator::factorial(n);
assert!(
result > 0,
"Factorial of {} should be positive, got {}",
n, result
);
}
}assert! checks a boolean. assert_eq! and assert_ne! compare values with helpful diff output on failure. All accept an optional format string as the last arguments for custom error messages.
Integration tests verify multiple components working together. They live in the tests/ directory and import your library as an external consumer would.
// Integration test file: tests/integration_test.rs
// Tests go in the tests/ directory
// They import the library like external code would
fn add_two(a: i32) -> i32 {
a + 2
fn multiply_by_three(a: i32) -> i32 {
a * 3
fn complex_operation(x: i32) -> i32 {
multiply_by_three(add_two(x))
#[test]
fn integration_test_complex_operation() {
// Test that multiple functions work together
assert_eq!(complex_operation(3), 15); // (3 + 2) * 3 = 15
assert_eq!(complex_operation(0), 6); // (0 + 2) * 3 = 6
assert_eq!(complex_operation(-2), 0); // (-2 + 2) * 3 = 0
}Unit tests live inside src/ in #[cfg(test)] modules and can test private functions. Integration tests live in tests/ and can only access your public API, just like a real consumer of your crate.
Documentation examples that double as executable tests. Write them in /// doc comments using fenced code blocks, cargo test runs them automatically.
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = add_docs(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// ```
/// assert_eq!(add_docs(-1, 1), 0);
/// ```
pub fn add_docs(a: i32, b: i32) -> i32 {
a + b
/// Demonstrates error handling in doc tests.
///
/// # Examples
///
/// ```
/// let result = divide_docs(10, 2);
/// assert!(result.is_ok());
/// assert_eq!(result.unwrap(), 5);
/// ```
///
/// ```
/// let result = divide_docs(5, 0);
/// assert!(result.is_err());
/// ```
pub fn divide_docs(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}Because doc tests are compiled and executed, your documentation examples never go stale. If you refactor an API, failing doc tests immediately flag outdated examples.
Organise tests into modules that mirror your source structure. Each module can have its own #[cfg(test)] block with focused, well-named tests.
// src/lib.rs - organising tests in modules
pub mod math {
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn subtract(a: i32, b: i32) -> i32 { a - b }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 2), 4); }
#[test]
fn test_subtract() { assert_eq!(subtract(5, 3), 2); }
}
pub mod string_utils {
pub fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reverse() {
assert_eq!(reverse("hello"), "olleh");
assert_eq!(reverse(""), "");
}
}
}Measure performance with Criterion.rs. Benchmarks live in the benches/ directory and help you track regressions.
// Benchmarks typically go in benches/ directory
// [dev-dependencies]
// criterion = "0.5"
fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
n => fibonacci(n - 1) + fibonacci(n - 2),
}
fn main() {
// Quick local benchmark without criterion
let start = std::time::Instant::now();
for _ in 0..1000 {
let _ = fibonacci(20);
}
let duration = start.elapsed();
println!("Fibonacci(20) x1000: {:?}", duration);
}Criterion.rs provides statistical analysis, warm-up iterations, and comparison against previous runs. It generates HTML reports with charts, far more reliable than manual timing with Instant.
Clippy is Rust's official linter that catches common mistakes and suggests idiomatic improvements. rustfmt enforces consistent formatting across your codebase.
// Clippy warnings and fixes
// BAD: unnecessary else block
fn bad_style(x: i32) -> i32 {
if x > 0 {
return x;
} else {
return -x;
}
// GOOD: idiomatic Rust
fn good_style(x: i32) -> i32 {
if x > 0 { x } else { -x }
// BAD: overly complex single-element Vec
fn inefficient() {
let v: Vec<_> = std::iter::once(42).collect();
println!("{:?}", v);
// GOOD: simple and direct
fn efficient() {
let v = vec![42];
println!("{:?}", v);
// Run: cargo clippy -- -D warnings
// Format: cargo fmt
// Check formatting: cargo fmt -- --checkRun cargo clippy -- -D warnings and cargo fmt -- --check in your CI pipeline. This catches style issues and common bugs before they reach code review.
A well-organised project layout makes navigation, testing, and collaboration easier. Follow Cargo's conventions.
// Recommended project structure:
//
// my_project/
// ├── Cargo.toml # Project manifest
// ├── Cargo.lock # Locked dependencies
// ├── src/
// │ ├── lib.rs # Library root
// │ ├── main.rs # Binary entry point
// │ ├── module1.rs # Public modules
// │ └── subdir/
// │ └── module2.rs
// ├── tests/ # Integration tests
// │ ├── integration_test.rs
// │ └── common.rs # Shared test utilities
// ├── benches/ # Benchmarks
// │ └── my_bench.rs
// └── examples/ # Runnable examples
// └── example1.rs# Cargo.toml best practices
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name"]
license = "MIT OR Apache-2.0"
description = "A brief description"
repository = "https://github.com/user/project"
keywords = ["keyword1", "keyword2"]
categories = ["command-line-utilities"]The Rust ecosystem has excellent crates for nearly every use case. Here are the most popular and battle-tested options organised by category.
// Essential crates ecosystem
// Serialisation: serde, serde_json, toml, bincode
// Web frameworks: axum, actix-web, rocket, warp
// Async runtime: tokio, async-std
// Database: sqlx, diesel, rusqlite, mongodb
// CLI tools: clap, structopt, argh
// Error handling: anyhow, thiserror, color-eyre
// Logging: tracing, log, env_logger
// Testing: proptest, quickcheck, rstest
// Performance: rayon (parallelism), parking_lot (Mutex)
// Utilities: itertools, regex, uuid, chrono, rand
#[cfg(test)]
mod crate_examples {
#[test]
fn common_patterns() {
// Using rayon for parallelism
let nums: Vec<i32> = (1..100).collect();
let sum: i32 = nums.iter().sum();
assert!(sum > 0);
}
}For any new project, consider starting with serde (serialisation), anyhow + thiserror (error handling), tracing (logging), and clap (CLI). These form a solid foundation for almost any Rust application.