| 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
| Task | Pattern |
|---|
| Create struct | struct Name { field: Type } |
| Create enum | enum Name { Variant1, Variant2(T) } |
| Implement trait | impl Trait for Type { ... } |
| Generic function | fn name<T: Trait>(x: T) -> T |
| Lifetime annotation | fn name<'a>(x: &'a str) -> &'a str |
| Error propagation | let x = fallible()?; |
| Pattern match | match value { Pattern => expr } |
| Iterate | for item in collection { ... } |
| Map/filter | iter.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 types | lang-rust-errors-dev |
| Configure Cargo.toml, add dependencies | lang-rust-cargo-dev |
| Design public APIs, publish crates | lang-rust-library-dev |
| Write documentation, rustdoc | lang-rust-docs-dev |
| Understand ownership deeply, unsafe code | lang-rust-memory-eng |
| Write benchmarks, measure performance | lang-rust-benchmarking-eng |
| Profile code, find bottlenecks | lang-rust-profiling-eng |
Core Types
Structs
struct User {
name: String,
email: String,
age: u32,
}
struct Point(f64, f64);
struct Marker;
let user = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
};
let user2 = User {
email: String::from("bob@example.com"),
..user
};
let User { name, email, .. } = user2;
Enums
enum Direction {
North,
South,
East,
West,
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
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
fn find_user(id: u32) -> Option<User> {
if id == 1 {
Some(User { })
} else {
None
}
}
match find_user(1) {
Some(user) => println!("Found: {}", user.name),
None => println!("Not found"),
}
let name = find_user(1)
.map(|u| u.name)
.unwrap_or_else(|| String::from("Anonymous"));
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)
}
fn process() -> Result<(), Error> {
let config = parse_config("config.json")?;
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"),
}
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 Some(user) = find_user(1) {
println!("Found: {}", user.name);
}
fn get_name(id: u32) -> String {
let Some(user) = find_user(id) else {
return String::from("Unknown");
};
user.name
}
Traits
Defining Traits
trait Summary {
fn summarize(&self) -> String;
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)
}
}
let article = Article { };
println!("{}", article.summarize());
Common Standard Traits
| Trait | Purpose | Derive? |
|---|
Debug | Debug formatting {:?} | Yes |
Clone | Explicit duplication | Yes |
Copy | Implicit copying | Yes (if all fields Copy) |
Default | Default value | Yes |
PartialEq / Eq | Equality comparison | Yes |
PartialOrd / Ord | Ordering | Yes |
Hash | Hash for HashMap keys | Yes |
Display | User-facing formatting | No |
From / Into | Type conversion | No |
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
name: String,
value: i32,
}
Trait Bounds
fn print_summary<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
fn process<T: Summary + Clone>(item: T) { }
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
}
}
impl<T: Display> Wrapper<T> {
fn print(&self) {
println!("{}", self.value);
}
}
Generic Enums
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Lifetimes
Basic Lifetime Annotations
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
let s1 = String::from("short");
let s2 = String::from("longer string");
let result = longest(&s1, &s2);
Lifetime in Structs
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce(&self, announcement: &str) -> &'a str {
println!("Attention: {announcement}");
self.text
}
}
Lifetime Elision
The compiler infers lifetimes in common cases:
fn first_word(s: &str) -> &str { }
fn first_word<'a>(s: &'a str) -> &'a str { }
Iterators
Creating Iterators
let v = vec![1, 2, 3, 4, 5];
for x in &v {
println!("{x}");
}
for x in v {
println!("{x}");
}
let mut v = vec![1, 2, 3];
for x in &mut v {
*x *= 2;
}
Iterator Adapters
let v = vec![1, 2, 3, 4, 5];
let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
let evens: Vec<_> = v.iter().filter(|x| *x % 2 == 0).collect();
let result: Vec<_> = v.iter()
.filter(|x| *x > 2)
.map(|x| x * 10)
.collect();
let found = v.iter().find(|x| **x > 3);
let sum: i32 = v.iter().fold(0, |acc, x| acc + x);
let sum: i32 = v.iter().sum();
Common Iterator Methods
| Method | Purpose |
|---|
map | Transform elements |
filter | Keep matching elements |
filter_map | Filter and transform in one |
flat_map | Map and flatten |
take(n) | First n elements |
skip(n) | Skip first n elements |
enumerate | Add index to elements |
zip | Combine two iterators |
collect | Collect into container |
fold | Reduce to single value |
find | First matching element |
any / all | Boolean predicates |
Closures
Closure Syntax
let add = |a: i32, b: i32| -> i32 { a + b };
let add = |a, b| a + b;
let double = |x| x * 2;
let multiplier = 3;
let multiply = |x| x * multiplier;
Closure Traits
| Trait | Captures | Can be called |
|---|
Fn | Immutable borrow | Multiple times |
FnMut | Mutable borrow | Multiple times |
FnOnce | Takes ownership | Once |
fn apply<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
let result = apply(|x| x * 2, 5);
Move Closures
let s = String::from("hello");
let print = move || println!("{s}");
print();
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 { }
}
}
let request = RequestBuilder::new("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.build();
Newtype Pattern
struct UserId(u64);
struct OrderId(u64);
fn process_user(id: UserId) { }
fn process_order(id: OrderId) { }
let user_id = UserId(1);
let order_id = OrderId(1);
Type State Pattern
struct Request<State> {
url: String,
_state: std::marker::PhantomData<State>,
}
struct Unvalidated;
struct Validated;
impl Request<Unvalidated> {
fn validate(self) -> Result<Request<Validated>, Error> {
Ok(Request {
url: self.url,
_state: std::marker::PhantomData,
})
}
}
impl Request<Validated> {
fn send(self) -> Response {
}
}
Troubleshooting
Ownership Errors
Problem: value borrowed here after move
let s = String::from("hello");
let s2 = s;
println!("{s}");
Fix: Clone if you need both, or use references:
let s = String::from("hello");
let s2 = s.clone();
println!("{s}");
Lifetime Errors
Problem: missing lifetime specifier
fn get_first(s: &str, t: &str) -> &str {
s
}
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);
}
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);
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
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
fn private_helper() -> i32 {
42
}
}
use math::add;
fn main() {
let sum = add(2, 3);
}
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)
mod config;
mod network;
use config::Settings;
use network::client::Client;
pub mod client;
pub mod server;
Visibility Rules
mod outer {
pub mod inner {
pub fn public_fn() {}
pub(crate) fn crate_fn() {}
pub(super) fn parent_fn() {}
pub(in crate::outer) fn outer_fn() {}
fn private_fn() {}
}
}
Re-exports
mod internal {
pub mod config {
pub struct Settings { }
}
pub mod network {
pub struct Client { }
}
}
pub use internal::config::Settings;
pub use internal::network::Client;
Prelude Pattern
pub use crate::config::Settings;
pub use crate::error::{Error, Result};
pub use crate::traits::{Serialize, Deserialize};
Path Types
| Path | Meaning |
|---|
crate:: | Start from crate root |
self:: | Current module |
super:: | Parent module |
::path | 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;
let handle = thread::spawn(|| {
println!("Hello from thread!");
});
handle.join().unwrap();
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Data: {:?}", data);
});
Message Passing (Channels)
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || {
tx.send("Hello").unwrap();
});
thread::spawn(move || {
tx2.send("World").unwrap();
});
for received in rx {
println!("Got: {received}");
}
Shared State (Mutex, Arc)
use std::sync::{Arc, Mutex};
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
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}");
}
#[tokio::main]
async fn main() {
process().await;
}
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 |
|---|
Send | Safe to send between threads |
Sync | Safe to share references between threads |
See also: patterns-concurrency-dev for cross-language concurrency patterns
Serialization
Rust uses the serde framework for serialization and deserialization.
Basic Serde Usage
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,
};
let json = serde_json::to_string(&user)?;
println!("{json}");
let parsed: User = serde_json::from_str(&json)?;
println!("{:?}", parsed);
Ok(())
}
Serde Attributes
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Config {
#[serde(rename = "api_key")]
key: String,
#[serde(default)]
retries: u32,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
#[serde(skip)]
internal_state: u32,
#[serde(flatten)]
metadata: Metadata,
}
#[derive(Serialize, Deserialize)]
struct Metadata {
version: String,
author: String,
}
Enum Serialization
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Message {
#[serde(rename = "text")]
Text { content: String },
#[serde(rename = "image")]
Image { url: String, width: u32 },
}
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
let yaml = serde_yaml::to_string(&data)?;
let parsed: Data = serde_yaml::from_str(&yaml)?;
let toml = toml::to_string(&data)?;
let parsed: Data = toml::from_str(&toml)?;
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
exact = "=1.0.0"
caret = "^1.2.3"
tilde = "~1.2.3"
wildcard = "1.*"
serde = { version = "1.0", features = ["derive"], default-features = false }
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" }
mylib = { path = "../mylib" }
serde_json = { version = "1.0", optional = true }
Common Cargo Commands
| Command | Purpose |
|---|
cargo build | Compile the project |
cargo build --release | Compile with optimizations |
cargo run | Build and run |
cargo test | Run tests |
cargo check | Fast type checking (no codegen) |
cargo clippy | Lint code |
cargo fmt | Format code |
cargo doc --open | Generate and open docs |
cargo update | Update dependencies |
cargo add <crate> | Add a dependency |
cargo tree | Show dependency tree |
Workspace Configuration
[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"] }
[package]
name = "myproject-core"
version.workspace = true
edition.workspace = true
[dependencies]
serde.workspace = true
Build Scripts
fn main() {
println!("cargo:rerun-if-changed=src/proto/schema.proto");
println!("cargo:rustc-env=BUILD_VERSION=1.0.0");
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
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
fn private_helper() -> i32 {
42
}
#[cfg(test)]
mod tests {
use super::*;
#[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() {
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
use myproject::add;
mod common;
#[test]
fn test_add_integration() {
common::setup();
assert_eq!(add(2, 3), 5);
}
Assertions
#[test]
fn test_assertions() {
assert_eq!(actual, expected);
assert_ne!(actual, not_expected);
assert!(condition);
assert!(!condition);
assert_eq!(result, 42, "Expected 42, got {}", result);
debug_assert!(condition);
}
Test Attributes
#[test]
fn normal_test() {}
#[test]
#[ignore]
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
cargo test test_name
cargo test -- --nocapture
cargo test -- --test-threads=1
cargo test --ignored
cargo test --test integration
Test Organization
mod parsing_tests {
use super::*;
#[test]
fn test_parse_number() { }
#[test]
fn test_parse_string() { }
}
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) {
}
}
#[test]
fn test_with_fixture() {
let fixture = TestFixture::new();
}
Mocking (with mockall)
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)
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!)
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
say_hello!();
macro_rules! create_function {
($name:ident) => {
fn $name() {
println!("Called {:?}", stringify!($name));
}
};
}
create_function!(foo);
foo();
macro_rules! vec_of_strings {
($($x:expr),* $(,)?) => {
vec![$($x.to_string()),*]
};
}
let v = vec_of_strings!["a", "b", "c"];
Fragment Specifiers
| Specifier | Matches | Example |
|---|
$x:ident | Identifier | foo, MyStruct |
$x:expr | Expression | 1 + 2, foo() |
$x:ty | Type | i32, Vec<String> |
$x:pat | Pattern | Some(x), _ |
$x:stmt | Statement | let x = 1; |
$x:block | Block | { ... } |
$x:item | Item | fn foo() {} |
$x:path | Path | std::io::Error |
$x:tt | Token tree | Any single token |
$x:literal | Literal | "hello", 42 |
Macro Patterns
macro_rules! calculate {
($e:expr) => { $e };
($left:expr, $op:tt, $right:expr) => {
$left $op $right
};
}
let a = calculate!(5);
let b = calculate!(5, +, 3);
macro_rules! sum {
($x:expr) => { $x };
($x:expr, $($rest:expr),+) => {
$x + sum!($($rest),+)
};
}
let total = sum!(1, 2, 3, 4);
Derive Macros
Derive macros generate trait implementations automatically.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct User {
name: String,
age: u32,
}
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Config {
host: String,
port: u16,
}
Creating Custom Derive Macros
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)
}
#[derive(MyTrait)]
struct MyStruct;
Derive Macro with Attributes
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
}
#[derive(Builder)]
struct Command {
#[builder(default = "false")]
verbose: bool,
#[builder(each = "arg")]
args: Vec<String>,
}
Attribute Macros
#[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)
}
#[route("/api/users")]
fn get_users() -> Response {
}
Function-like Procedural Macros
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let query = input.value();
let expanded = quote! {
Query::new(#query)
};
TokenStream::from(expanded)
}
let query = sql!("SELECT * FROM users WHERE id = $1");
Common Proc-Macro Crates
| Crate | Purpose | Example |
|---|
syn | Parse Rust code | parse_macro_input! |
quote | Generate Rust code | quote! { ... } |
proc-macro2 | TokenStream utilities | Span manipulation |
darling | Derive macro helpers | Attribute parsing |
Macro Hygiene
macro_rules! using_x {
($e:expr) => {
{
let x = 42;
$e
}
};
}
let x = 10;
let result = using_x!(x + 1);
assert_eq!(result, 11);
Debug Macros
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