AgentSkillsCN

lang-rust-dev

涵盖核心语法、特质、泛型、生命周期以及常用惯用法的 Rust 基础模式。当您需要编写 Rust 代码、理解所有权基础、处理 Option/Result 类型,或在选择 Rust 专业技能时寻求指导时,可选用此技能。这是 Rust 开发的入门之选。

SKILL.md
--- frontmatter
name: lang-rust-dev
description: Foundational Rust patterns covering core syntax, traits, generics, lifetimes, and common idioms. Use when writing Rust code, understanding ownership basics, working with Option/Result, or needing guidance on which specialized Rust skill to use. This is the entry point for Rust development.

Rust Fundamentals

Foundational Rust patterns and core language features. This skill serves as both a reference for common patterns and an index to specialized Rust skills.

Overview

code
┌─────────────────────────────────────────────────────────────────┐
│                      Rust Skill Hierarchy                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌───────────────────┐                        │
│                    │   lang-rust-dev   │ ◄── You are here       │
│                    │   (foundation)    │                        │
│                    └─────────┬─────────┘                        │
│                              │                                  │
│     ┌────────────┬───────────┼───────────┬────────────┐        │
│     │            │           │           │            │        │
│     ▼            ▼           ▼           ▼            ▼        │
│ ┌────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐ ┌──────────┐   │
│ │ errors │ │  cargo   │ │library │ │ memory  │ │ profiling│   │
│ │  -dev  │ │   -dev   │ │  -dev  │ │  -eng   │ │   -eng   │   │
│ └────────┘ └──────────┘ └────────┘ └─────────┘ └──────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

This skill covers:

  • Core syntax (structs, enums, match, impl blocks)
  • Traits and generics basics
  • Lifetime fundamentals
  • Option and Result patterns
  • Iterators and closures
  • Common idioms and conventions

This skill does NOT cover (see specialized skills):

  • Error handling with error-stack → lang-rust-errors-dev
  • Cargo.toml and dependencies → lang-rust-cargo-dev
  • Library/crate publishing → lang-rust-library-dev
  • Documentation patterns → lang-rust-docs-dev
  • Memory safety engineering → lang-rust-memory-eng
  • Benchmarking → lang-rust-benchmarking-eng
  • Profiling/debugging → lang-rust-profiling-eng

Quick Reference

TaskPattern
Create structstruct Name { field: Type }
Create enumenum Name { Variant1, Variant2(T) }
Implement traitimpl Trait for Type { ... }
Generic functionfn name<T: Trait>(x: T) -> T
Lifetime annotationfn name<'a>(x: &'a str) -> &'a str
Error propagationlet x = fallible()?;
Pattern matchmatch value { Pattern => expr }
Iteratefor item in collection { ... }
Map/filteriter.map(|x| ...).filter(|x| ...)

Skill Routing

Use this table to find the right specialized skill:

When you need to...Use this skill
Handle errors with Result/Report typeslang-rust-errors-dev
Configure Cargo.toml, add dependencieslang-rust-cargo-dev
Design public APIs, publish crateslang-rust-library-dev
Write documentation, rustdoclang-rust-docs-dev
Understand ownership deeply, unsafe codelang-rust-memory-eng
Write benchmarks, measure performancelang-rust-benchmarking-eng
Profile code, find bottleneckslang-rust-profiling-eng

Core Types

Structs

rust
// Named fields
struct User {
    name: String,
    email: String,
    age: u32,
}

// Tuple struct
struct Point(f64, f64);

// Unit struct
struct Marker;

// Creating instances
let user = User {
    name: String::from("Alice"),
    email: String::from("alice@example.com"),
    age: 30,
};

// Struct update syntax
let user2 = User {
    email: String::from("bob@example.com"),
    ..user  // Take remaining fields from user
};

// Destructuring
let User { name, email, .. } = user2;

Enums

rust
// Simple enum
enum Direction {
    North,
    South,
    East,
    West,
}

// Enum with data
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

// Using enums
let msg = Message::Move { x: 10, y: 20 };

match msg {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move to {x}, {y}"),
    Message::Write(text) => println!("Write: {text}"),
    Message::ChangeColor(r, g, b) => println!("Color: {r},{g},{b}"),
}

Option and Result

rust
// Option: value that might not exist
fn find_user(id: u32) -> Option<User> {
    if id == 1 {
        Some(User { /* ... */ })
    } else {
        None
    }
}

// Using Option
match find_user(1) {
    Some(user) => println!("Found: {}", user.name),
    None => println!("Not found"),
}

// Option methods
let name = find_user(1)
    .map(|u| u.name)
    .unwrap_or_else(|| String::from("Anonymous"));

// Result: operation that might fail
fn parse_config(path: &str) -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string(path)?;
    let config = serde_json::from_str(&content)?;
    Ok(config)
}

// Error propagation with ?
fn process() -> Result<(), Error> {
    let config = parse_config("config.json")?;  // Returns early on error
    // ... use config
    Ok(())
}

Pattern Matching

Match Expressions

rust
let x = 5;

match x {
    1 => println!("one"),
    2 | 3 => println!("two or three"),
    4..=6 => println!("four through six"),
    n if n > 10 => println!("greater than ten: {n}"),
    _ => println!("something else"),
}

// Destructuring in match
let point = (3, 4);
match point {
    (0, 0) => println!("origin"),
    (x, 0) => println!("on x-axis at {x}"),
    (0, y) => println!("on y-axis at {y}"),
    (x, y) => println!("at ({x}, {y})"),
}

If Let and Let Else

rust
// if let: match single pattern
if let Some(user) = find_user(1) {
    println!("Found: {}", user.name);
}

// let else: match or diverge
fn get_name(id: u32) -> String {
    let Some(user) = find_user(id) else {
        return String::from("Unknown");
    };
    user.name
}

Traits

Defining Traits

rust
trait Summary {
    // Required method
    fn summarize(&self) -> String;

    // Default implementation
    fn preview(&self) -> String {
        format!("{}...", &self.summarize()[..50])
    }
}

Implementing Traits

rust
struct Article {
    title: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
}

// Use the trait
let article = Article { /* ... */ };
println!("{}", article.summarize());

Common Standard Traits

TraitPurposeDerive?
DebugDebug formatting {:?}Yes
CloneExplicit duplicationYes
CopyImplicit copyingYes (if all fields Copy)
DefaultDefault valueYes
PartialEq / EqEquality comparisonYes
PartialOrd / OrdOrderingYes
HashHash for HashMap keysYes
DisplayUser-facing formattingNo
From / IntoType conversionNo
rust
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
    name: String,
    value: i32,
}

Trait Bounds

rust
// Function with trait bound
fn print_summary<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// Multiple bounds
fn process<T: Summary + Clone>(item: T) { /* ... */ }

// Where clause (cleaner for complex bounds)
fn complex<T, U>(t: T, u: U) -> String
where
    T: Summary + Clone,
    U: Debug + Default,
{
    // ...
}

Generics

Generic Functions

rust
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

Generic Structs

rust
struct Wrapper<T> {
    value: T,
}

impl<T> Wrapper<T> {
    fn new(value: T) -> Self {
        Wrapper { value }
    }

    fn get(&self) -> &T {
        &self.value
    }
}

// Conditional implementation
impl<T: Display> Wrapper<T> {
    fn print(&self) {
        println!("{}", self.value);
    }
}

Generic Enums

rust
// Option and Result are generic enums
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Lifetimes

Basic Lifetime Annotations

rust
// Lifetime ensures returned reference is valid
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

// Usage
let s1 = String::from("short");
let s2 = String::from("longer string");
let result = longest(&s1, &s2);

Lifetime in Structs

rust
// Struct containing references
struct Excerpt<'a> {
    text: &'a str,
}

impl<'a> Excerpt<'a> {
    fn level(&self) -> i32 {
        3  // Doesn't use the reference, no annotation needed
    }

    fn announce(&self, announcement: &str) -> &'a str {
        println!("Attention: {announcement}");
        self.text
    }
}

Lifetime Elision

The compiler infers lifetimes in common cases:

rust
// These are equivalent:
fn first_word(s: &str) -> &str { /* ... */ }
fn first_word<'a>(s: &'a str) -> &'a str { /* ... */ }

// Rules:
// 1. Each input reference gets its own lifetime
// 2. If exactly one input lifetime, output gets same
// 3. If &self, output gets lifetime of self

Iterators

Creating Iterators

rust
let v = vec![1, 2, 3, 4, 5];

// Borrowing iterator
for x in &v {
    println!("{x}");
}

// Consuming iterator
for x in v {
    println!("{x}");
}

// Mutable iterator
let mut v = vec![1, 2, 3];
for x in &mut v {
    *x *= 2;
}

Iterator Adapters

rust
let v = vec![1, 2, 3, 4, 5];

// map: transform each element
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();

// filter: keep matching elements
let evens: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect();

// chain adapters
let result: Vec<_> = v.iter()
    .filter(|x| *x > 2)
    .map(|x| x * 10)
    .collect();

// find: first matching element
let found = v.iter().find(|x| **x > 3);

// fold: accumulate
let sum: i32 = v.iter().fold(0, |acc, x| acc + x);
// Or use sum()
let sum: i32 = v.iter().sum();

Common Iterator Methods

MethodPurpose
mapTransform elements
filterKeep matching elements
filter_mapFilter and transform in one
flat_mapMap and flatten
take(n)First n elements
skip(n)Skip first n elements
enumerateAdd index to elements
zipCombine two iterators
collectCollect into container
foldReduce to single value
findFirst matching element
any / allBoolean predicates

Closures

Closure Syntax

rust
// Full syntax
let add = |a: i32, b: i32| -> i32 { a + b };

// Type inference
let add = |a, b| a + b;

// Single expression (no braces needed)
let double = |x| x * 2;

// Capturing environment
let multiplier = 3;
let multiply = |x| x * multiplier;

Closure Traits

TraitCapturesCan be called
FnImmutable borrowMultiple times
FnMutMutable borrowMultiple times
FnOnceTakes ownershipOnce
rust
// Function taking a closure
fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

let result = apply(|x| x * 2, 5);

Move Closures

rust
// Force closure to take ownership
let s = String::from("hello");
let print = move || println!("{s}");
// s is no longer valid here

print();  // Works because closure owns s

Common Idioms

Builder Pattern

rust
struct RequestBuilder {
    url: String,
    method: String,
    headers: Vec<(String, String)>,
}

impl RequestBuilder {
    fn new(url: impl Into<String>) -> Self {
        Self {
            url: url.into(),
            method: String::from("GET"),
            headers: Vec::new(),
        }
    }

    fn method(mut self, method: impl Into<String>) -> Self {
        self.method = method.into();
        self
    }

    fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.headers.push((key.into(), value.into()));
        self
    }

    fn build(self) -> Request {
        Request { /* ... */ }
    }
}

// Usage
let request = RequestBuilder::new("https://api.example.com")
    .method("POST")
    .header("Content-Type", "application/json")
    .build();

Newtype Pattern

rust
// Wrap primitive types for type safety
struct UserId(u64);
struct OrderId(u64);

fn process_user(id: UserId) { /* ... */ }
fn process_order(id: OrderId) { /* ... */ }

// Can't mix them up
let user_id = UserId(1);
let order_id = OrderId(1);
// process_user(order_id);  // Compile error!

Type State Pattern

rust
// Compile-time state machine
struct Request<State> {
    url: String,
    _state: std::marker::PhantomData<State>,
}

struct Unvalidated;
struct Validated;

impl Request<Unvalidated> {
    fn validate(self) -> Result<Request<Validated>, Error> {
        // Validation logic
        Ok(Request {
            url: self.url,
            _state: std::marker::PhantomData,
        })
    }
}

impl Request<Validated> {
    fn send(self) -> Response {
        // Only valid requests can be sent
    }
}

Troubleshooting

Ownership Errors

Problem: value borrowed here after move

rust
let s = String::from("hello");
let s2 = s;
println!("{s}");  // Error: s was moved to s2

Fix: Clone if you need both, or use references:

rust
let s = String::from("hello");
let s2 = s.clone();
println!("{s}");  // Works

Lifetime Errors

Problem: missing lifetime specifier

rust
fn get_first(s: &str, t: &str) -> &str {
    s  // Error: which lifetime?
}

Fix: Add explicit lifetimes:

rust
fn get_first<'a>(s: &'a str, _t: &str) -> &'a str {
    s
}

Trait Bound Errors

Problem: the trait X is not implemented for Y

rust
fn print_it<T>(x: T) {
    println!("{}", x);  // Error: T doesn't implement Display
}

Fix: Add trait bound:

rust
fn print_it<T: std::fmt::Display>(x: T) {
    println!("{}", x);
}

Mutability Errors

Problem: cannot borrow as mutable

rust
let v = vec![1, 2, 3];
v.push(4);  // Error: v is not mutable

Fix: Make it mutable:

rust
let mut v = vec![1, 2, 3];
v.push(4);

Module System

Rust uses a module system to organize code into logical units with explicit visibility control.

Module Basics

rust
// src/lib.rs or src/main.rs

// Inline module
mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    fn private_helper() -> i32 {
        42
    }
}

// Use items from module
use math::add;

fn main() {
    let sum = add(2, 3);
    // math::private_helper();  // Error: function is private
}

File-Based Modules

code
src/
├── main.rs          # Crate root
├── lib.rs           # Library crate root (if both bin and lib)
├── config.rs        # mod config;
└── network/
    ├── mod.rs       # mod network;
    ├── client.rs    # mod client; (in mod.rs)
    └── server.rs    # mod server; (in mod.rs)
rust
// src/main.rs
mod config;           // Loads from src/config.rs
mod network;          // Loads from src/network/mod.rs

use config::Settings;
use network::client::Client;

// src/network/mod.rs
pub mod client;       // Loads from src/network/client.rs
pub mod server;

Visibility Rules

rust
mod outer {
    pub mod inner {
        pub fn public_fn() {}           // Visible everywhere
        pub(crate) fn crate_fn() {}     // Visible in crate
        pub(super) fn parent_fn() {}    // Visible in parent module
        pub(in crate::outer) fn outer_fn() {}  // Visible in specific path
        fn private_fn() {}              // Only this module
    }
}

Re-exports

rust
// src/lib.rs - Flatten the public API
mod internal {
    pub mod config {
        pub struct Settings { /* ... */ }
    }
    pub mod network {
        pub struct Client { /* ... */ }
    }
}

// Re-export for cleaner external API
pub use internal::config::Settings;
pub use internal::network::Client;

// External users can now:
// use mycrate::Settings;
// Instead of:
// use mycrate::internal::config::Settings;

Prelude Pattern

rust
// src/prelude.rs
pub use crate::config::Settings;
pub use crate::error::{Error, Result};
pub use crate::traits::{Serialize, Deserialize};

// Users can import everything commonly needed:
// use mycrate::prelude::*;

Path Types

PathMeaning
crate::Start from crate root
self::Current module
super::Parent module
::pathExternal crate (Rust 2018+: crate name)

Concurrency

Rust provides fearless concurrency through its ownership system. The type system prevents data races at compile time.

Threads

rust
use std::thread;

// Spawn a thread
let handle = thread::spawn(|| {
    println!("Hello from thread!");
});

handle.join().unwrap();  // Wait for thread to finish

// Move data into thread
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
    println!("Data: {:?}", data);
});

Message Passing (Channels)

rust
use std::sync::mpsc;  // Multiple producer, single consumer

let (tx, rx) = mpsc::channel();

// Clone sender for multiple producers
let tx2 = tx.clone();

thread::spawn(move || {
    tx.send("Hello").unwrap();
});

thread::spawn(move || {
    tx2.send("World").unwrap();
});

// Receive messages
for received in rx {
    println!("Got: {received}");
}

Shared State (Mutex, Arc)

rust
use std::sync::{Arc, Mutex};

// Arc: Atomic Reference Counted (thread-safe Rc)
// Mutex: Mutual exclusion for safe mutable access
let counter = Arc::new(Mutex::new(0));

let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

for handle in handles {
    handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());

Async/Await

rust
// Requires async runtime (tokio, async-std)
// Cargo.toml: tokio = { version = "1", features = ["full"] }

use tokio;

async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    reqwest::get(url).await?.text().await
}

async fn process() {
    let data = fetch_data("https://api.example.com").await.unwrap();
    println!("{data}");
}

// Run async main
#[tokio::main]
async fn main() {
    process().await;
}

// Concurrent execution
async fn fetch_all() {
    let (a, b) = tokio::join!(
        fetch_data("https://api.example.com/a"),
        fetch_data("https://api.example.com/b"),
    );
}

Send and Sync Traits

TraitMeaning
SendSafe to send between threads
SyncSafe to share references between threads
rust
// Most types are Send + Sync automatically
// Rc is not Send (use Arc instead)
// Cell/RefCell are not Sync (use Mutex instead)

See also: patterns-concurrency-dev for cross-language concurrency patterns


Serialization

Rust uses the serde framework for serialization and deserialization.

Basic Serde Usage

rust
// Cargo.toml:
// serde = { version = "1.0", features = ["derive"] }
// serde_json = "1.0"

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    name: String,
    email: String,
    age: u32,
}

fn main() -> Result<(), serde_json::Error> {
    let user = User {
        name: String::from("Alice"),
        email: String::from("alice@example.com"),
        age: 30,
    };

    // Serialize to JSON
    let json = serde_json::to_string(&user)?;
    println!("{json}");

    // Deserialize from JSON
    let parsed: User = serde_json::from_str(&json)?;
    println!("{:?}", parsed);

    Ok(())
}

Serde Attributes

rust
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]  // All fields as camelCase
struct Config {
    #[serde(rename = "api_key")]    // Custom field name
    key: String,

    #[serde(default)]               // Use Default if missing
    retries: u32,

    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,

    #[serde(skip)]                  // Never serialize/deserialize
    internal_state: u32,

    #[serde(flatten)]               // Flatten nested struct
    metadata: Metadata,
}

#[derive(Serialize, Deserialize)]
struct Metadata {
    version: String,
    author: String,
}

Enum Serialization

rust
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]  // Internally tagged
enum Message {
    #[serde(rename = "text")]
    Text { content: String },

    #[serde(rename = "image")]
    Image { url: String, width: u32 },
}

// Serializes as: {"type": "text", "content": "Hello"}

Custom Serialization

rust
use serde::{Serializer, Deserializer};

#[derive(Serialize, Deserialize)]
struct Data {
    #[serde(serialize_with = "serialize_as_string")]
    #[serde(deserialize_with = "deserialize_from_string")]
    value: u64,
}

fn serialize_as_string<S>(value: &u64, s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    s.serialize_str(&value.to_string())
}

fn deserialize_from_string<'de, D>(d: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    let s: String = Deserialize::deserialize(d)?;
    s.parse().map_err(serde::de::Error::custom)
}

Other Formats

rust
// YAML: serde_yaml = "0.9"
let yaml = serde_yaml::to_string(&data)?;
let parsed: Data = serde_yaml::from_str(&yaml)?;

// TOML: toml = "0.8"
let toml = toml::to_string(&data)?;
let parsed: Data = toml::from_str(&toml)?;

// MessagePack: rmp-serde = "1.1"
let msgpack = rmp_serde::to_vec(&data)?;
let parsed: Data = rmp_serde::from_slice(&msgpack)?;

See also: patterns-serialization-dev for cross-language serialization patterns


Build and Dependencies

Rust uses Cargo as its build system and package manager.

Cargo.toml Basics

toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A brief description"
license = "MIT OR Apache-2.0"
repository = "https://github.com/user/project"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = "0.11"

[dev-dependencies]
criterion = "0.5"

[build-dependencies]
cc = "1.0"

[features]
default = ["json"]
json = ["serde_json"]
full = ["json", "yaml"]

[[bin]]
name = "myapp"
path = "src/main.rs"

[[bench]]
name = "my_benchmark"
harness = false

Dependency Specification

toml
# Version requirements
exact = "=1.0.0"        # Exactly 1.0.0
caret = "^1.2.3"        # >=1.2.3, <2.0.0 (default)
tilde = "~1.2.3"        # >=1.2.3, <1.3.0
wildcard = "1.*"        # >=1.0.0, <2.0.0

# Features
serde = { version = "1.0", features = ["derive"], default-features = false }

# Git dependencies
mylib = { git = "https://github.com/user/mylib", branch = "main" }
mylib = { git = "https://github.com/user/mylib", tag = "v1.0.0" }
mylib = { git = "https://github.com/user/mylib", rev = "abc123" }

# Path dependencies (local development)
mylib = { path = "../mylib" }

# Optional dependencies
serde_json = { version = "1.0", optional = true }

Common Cargo Commands

CommandPurpose
cargo buildCompile the project
cargo build --releaseCompile with optimizations
cargo runBuild and run
cargo testRun tests
cargo checkFast type checking (no codegen)
cargo clippyLint code
cargo fmtFormat code
cargo doc --openGenerate and open docs
cargo updateUpdate dependencies
cargo add <crate>Add a dependency
cargo treeShow dependency tree

Workspace Configuration

toml
# Root Cargo.toml
[workspace]
members = [
    "crates/core",
    "crates/cli",
    "crates/web",
]
resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
toml
# crates/core/Cargo.toml
[package]
name = "myproject-core"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true

Build Scripts

rust
// build.rs - Runs before compilation
fn main() {
    // Tell Cargo to rerun if file changes
    println!("cargo:rerun-if-changed=src/proto/schema.proto");

    // Set environment variable for compilation
    println!("cargo:rustc-env=BUILD_VERSION=1.0.0");

    // Add link search path
    println!("cargo:rustc-link-search=/usr/local/lib");
}

See also: lang-rust-cargo-dev for advanced Cargo configuration


Testing

Rust has built-in testing support with cargo test.

Unit Tests

rust
// src/lib.rs

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn private_helper() -> i32 {
    42
}

#[cfg(test)]
mod tests {
    use super::*;  // Import from parent module

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_add_negative() {
        assert_eq!(add(-1, 1), 0);
    }

    #[test]
    fn test_private_helper() {
        // Can test private functions
        assert_eq!(private_helper(), 42);
    }

    #[test]
    #[should_panic(expected = "divide by zero")]
    fn test_panic() {
        divide(1, 0);
    }

    #[test]
    fn test_result() -> Result<(), String> {
        let result = parse_number("42")?;
        assert_eq!(result, 42);
        Ok(())
    }
}

Integration Tests

code
tests/
├── integration_test.rs    # Each file is a separate test crate
└── common/
    └── mod.rs             # Shared test utilities
rust
// tests/integration_test.rs
use myproject::add;

mod common;  // Load shared utilities

#[test]
fn test_add_integration() {
    common::setup();
    assert_eq!(add(2, 3), 5);
}

Assertions

rust
#[test]
fn test_assertions() {
    // Equality
    assert_eq!(actual, expected);
    assert_ne!(actual, not_expected);

    // Boolean
    assert!(condition);
    assert!(!condition);

    // With custom message
    assert_eq!(result, 42, "Expected 42, got {}", result);

    // Debug assertions (only in debug builds)
    debug_assert!(condition);
}

Test Attributes

rust
#[test]
fn normal_test() {}

#[test]
#[ignore]  // Skip by default, run with --ignored
fn slow_test() {}

#[test]
#[should_panic]
fn test_panics() {
    panic!("This should panic");
}

#[test]
#[should_panic(expected = "specific message")]
fn test_specific_panic() {
    panic!("specific message here");
}

Running Tests

bash
cargo test                    # Run all tests
cargo test test_name          # Run tests matching name
cargo test -- --nocapture     # Show println! output
cargo test -- --test-threads=1  # Run sequentially
cargo test --ignored          # Run ignored tests
cargo test --test integration # Run specific test file

Test Organization

rust
// Group related tests
mod parsing_tests {
    use super::*;

    #[test]
    fn test_parse_number() { /* ... */ }

    #[test]
    fn test_parse_string() { /* ... */ }
}

// Setup and teardown
struct TestFixture {
    temp_dir: tempfile::TempDir,
}

impl TestFixture {
    fn new() -> Self {
        Self {
            temp_dir: tempfile::tempdir().unwrap(),
        }
    }
}

impl Drop for TestFixture {
    fn drop(&mut self) {
        // Cleanup happens automatically
    }
}

#[test]
fn test_with_fixture() {
    let fixture = TestFixture::new();
    // Use fixture.temp_dir
}

Mocking (with mockall)

rust
// Cargo.toml: mockall = "0.11"

use mockall::{automock, predicate::*};

#[automock]
trait Database {
    fn get_user(&self, id: u32) -> Option<User>;
}

#[test]
fn test_with_mock() {
    let mut mock = MockDatabase::new();
    mock.expect_get_user()
        .with(eq(1))
        .returning(|_| Some(User { name: "Alice".into() }));

    let result = process_user(&mock, 1);
    assert!(result.is_ok());
}

Property-Based Testing (with proptest)

rust
// Cargo.toml: proptest = "1.0"

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_add_commutative(a: i32, b: i32) {
        prop_assert_eq!(add(a, b), add(b, a));
    }

    #[test]
    fn test_parse_roundtrip(s in "[a-z]{1,10}") {
        let parsed = parse(&s);
        prop_assert!(parsed.is_ok());
    }
}

Metaprogramming

Rust provides powerful metaprogramming through macros. There are two main types: declarative macros (macro_rules!) and procedural macros (derive, attribute, and function-like).

Declarative Macros (macro_rules!)

rust
// Simple macro
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

say_hello!();  // Prints: Hello!

// Macro with arguments
macro_rules! create_function {
    ($name:ident) => {
        fn $name() {
            println!("Called {:?}", stringify!($name));
        }
    };
}

create_function!(foo);
foo();  // Prints: Called "foo"

// Macro with expression repetition
macro_rules! vec_of_strings {
    ($($x:expr),* $(,)?) => {
        vec![$($x.to_string()),*]
    };
}

let v = vec_of_strings!["a", "b", "c"];

Fragment Specifiers

SpecifierMatchesExample
$x:identIdentifierfoo, MyStruct
$x:exprExpression1 + 2, foo()
$x:tyTypei32, Vec<String>
$x:patPatternSome(x), _
$x:stmtStatementlet x = 1;
$x:blockBlock{ ... }
$x:itemItemfn foo() {}
$x:pathPathstd::io::Error
$x:ttToken treeAny single token
$x:literalLiteral"hello", 42

Macro Patterns

rust
// Multiple match arms
macro_rules! calculate {
    // Single value
    ($e:expr) => { $e };
    // Two values with operator
    ($left:expr, $op:tt, $right:expr) => {
        $left $op $right
    };
}

let a = calculate!(5);        // 5
let b = calculate!(5, +, 3);  // 8

// Recursive macro for variadic arguments
macro_rules! sum {
    ($x:expr) => { $x };
    ($x:expr, $($rest:expr),+) => {
        $x + sum!($($rest),+)
    };
}

let total = sum!(1, 2, 3, 4);  // 10

Derive Macros

Derive macros generate trait implementations automatically.

rust
// Using built-in derives
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct User {
    name: String,
    age: u32,
}

// Using third-party derives
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Config {
    host: String,
    port: u16,
}

Creating Custom Derive Macros

rust
// In a proc-macro crate (Cargo.toml: proc-macro = true)
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyTrait)]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let expanded = quote! {
        impl MyTrait for #name {
            fn describe(&self) -> String {
                format!("This is a {}", stringify!(#name))
            }
        }
    };

    TokenStream::from(expanded)
}

// Usage
#[derive(MyTrait)]
struct MyStruct;

Derive Macro with Attributes

rust
// Macro definition
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    // Parse #[builder(...)] attributes
    // Generate builder pattern implementation
    // ...
}

// Usage
#[derive(Builder)]
struct Command {
    #[builder(default = "false")]
    verbose: bool,

    #[builder(each = "arg")]
    args: Vec<String>,
}

Attribute Macros

rust
// Macro definition (in proc-macro crate)
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attr = parse_macro_input!(attr as LitStr);
    let item = parse_macro_input!(item as ItemFn);
    let fn_name = &item.sig.ident;

    let expanded = quote! {
        #item

        inventory::submit! {
            Route {
                path: #attr,
                handler: #fn_name,
            }
        }
    };

    TokenStream::from(expanded)
}

// Usage
#[route("/api/users")]
fn get_users() -> Response {
    // ...
}

Function-like Procedural Macros

rust
// Macro definition
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as LitStr);
    let query = input.value();

    // Validate SQL at compile time
    // Generate typed query code
    let expanded = quote! {
        Query::new(#query)
    };

    TokenStream::from(expanded)
}

// Usage
let query = sql!("SELECT * FROM users WHERE id = $1");

Common Proc-Macro Crates

CratePurposeExample
synParse Rust codeparse_macro_input!
quoteGenerate Rust codequote! { ... }
proc-macro2TokenStream utilitiesSpan manipulation
darlingDerive macro helpersAttribute parsing

Macro Hygiene

rust
macro_rules! using_x {
    ($e:expr) => {
        {
            let x = 42;  // This x is hygienic
            $e           // $e refers to caller's x
        }
    };
}

let x = 10;
let result = using_x!(x + 1);  // Uses caller's x=10, not macro's x=42
assert_eq!(result, 11);

Debug Macros

rust
// Print macro expansion
// Run: cargo expand
// Or: cargo expand --lib path::to::module

// Compile-time debugging
macro_rules! debug_macro {
    ($($arg:tt)*) => {
        compile_error!(concat!("Debug: ", stringify!($($arg)*)));
    };
}

See Also

  • patterns-metaprogramming-dev - Cross-language macro/decorator patterns

Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev - Async/await, threads, channels
  • patterns-serialization-dev - JSON, validation, struct tags
  • patterns-metaprogramming-dev - Decorators, macros, annotations

References