Back to list
aRustyDev

lang-rust-dev

by aRustyDev

AI Contexts, Commands, Plugins, Docs, Examples, Specs, Etc

1🍴 1📅 Jan 16, 2026

SKILL.md


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

┌─────────────────────────────────────────────────────────────────┐
│                      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

// 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

// 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

// 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

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

// 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

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

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

Implementing Traits

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
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
    name: String,
    value: i32,
}

Trait Bounds

// 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

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

Generic Structs

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

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

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

Lifetimes

Basic Lifetime Annotations

// 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

// 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:

// 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

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

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

// 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
// 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

// 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

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

// 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

// 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

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:

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

Lifetime Errors

Problem: missing lifetime specifier

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

Fix: Add explicit lifetimes:

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

Trait Bound Errors

Problem: the trait X is not implemented for Y

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

Fix: Add trait bound:

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

Mutability Errors

Problem: cannot borrow as mutable

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

Fix: Make it mutable:

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

// 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

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)
// 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

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

// 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

// 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

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)

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)

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

// 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
// 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

// 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

#[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

#[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

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

// 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

[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

# 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

# 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"] }
# crates/core/Cargo.toml
[package]
name = "myproject-core"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true

Build Scripts

// 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

// 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

tests/
├── integration_test.rs    # Each file is a separate test crate
└── common/
    └── mod.rs             # Shared test utilities
// 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

#[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

#[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

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

// 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)

// 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)

// 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!)

// 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

// 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.

// 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

// 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

// 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

// 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

// 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

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

// 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

Score

Total Score

55/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

0/5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon