Claude-skill-registry lang-rust-dev
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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-rust-dev" ~/.claude/skills/majiayu000-claude-skill-registry-lang-rust-dev && rm -rf "$T"
skills/data/lang-rust-dev/SKILL.mdRust 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
| Task | Pattern |
|---|---|
| Create struct | |
| Create enum | |
| Implement trait | |
| Generic function | |
| Lifetime annotation | |
| Error propagation | |
| Pattern match | |
| Iterate | |
| Map/filter | |
Skill Routing
Use this table to find the right specialized skill:
| When you need to... | Use this skill |
|---|---|
| Handle errors with Result/Report types | |
| Configure Cargo.toml, add dependencies | |
| Design public APIs, publish crates | |
| Write documentation, rustdoc | |
| Understand ownership deeply, unsafe code | |
| Write benchmarks, measure performance | |
| Profile code, find bottlenecks | |
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
| Trait | Purpose | Derive? |
|---|---|---|
| Debug formatting | Yes |
| Explicit duplication | Yes |
| Implicit copying | Yes (if all fields Copy) |
| Default value | Yes |
/ | Equality comparison | Yes |
/ | Ordering | Yes |
| Hash for HashMap keys | Yes |
| User-facing formatting | No |
/ | Type conversion | No |
#[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
| Method | Purpose |
|---|---|
| Transform elements |
| Keep matching elements |
| Filter and transform in one |
| Map and flatten |
| First n elements |
| Skip first n elements |
| Add index to elements |
| Combine two iterators |
| Collect into container |
| Reduce to single value |
| First matching element |
/ | Boolean 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
| Trait | Captures | Can be called |
|---|---|---|
| Immutable borrow | Multiple times |
| Mutable borrow | Multiple times |
| Takes ownership | Once |
// 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
| Path | Meaning |
|---|---|
| Start from crate root |
| Current module |
| Parent module |
| External 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
| Trait | Meaning |
|---|---|
| Safe to send between threads |
| Safe 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
| Command | Purpose |
|---|---|
| Compile the project |
| Compile with optimizations |
| Build and run |
| Run tests |
| Fast type checking (no codegen) |
| Lint code |
| Format code |
| Generate and open docs |
| Update dependencies |
| Add a dependency |
| Show 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
| Specifier | Matches | Example |
|---|---|---|
| Identifier | , |
| Expression | , |
| Type | , |
| Pattern | , |
| Statement | |
| Block | |
| Item | |
| Path | |
| Token tree | Any single token |
| Literal | , |
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
| Crate | Purpose | Example |
|---|---|---|
| Parse Rust code | |
| Generate Rust code | |
| TokenStream utilities | Span manipulation |
| Derive macro helpers | Attribute 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
- Cross-language macro/decorator patternspatterns-metaprogramming-dev
Cross-Cutting Patterns
For cross-language comparison and translation patterns, see:
- Async/await, threads, channelspatterns-concurrency-dev
- JSON, validation, struct tagspatterns-serialization-dev
- Decorators, macros, annotationspatterns-metaprogramming-dev
References
- The Rust Book
- Rust by Example
- Rust Reference
- Specialized skills:
,lang-rust-errors-dev
,lang-rust-cargo-dev
,lang-rust-library-dev
,lang-rust-docs-devlang-rust-memory-eng