35. Macros: macro_rules! and what derive macros do
Rust macros operate on syntax trees, not text, so the problems of the C preprocessor (double evaluation, missing parentheses, name capture) do not occur.
use std::collections::HashMap;
// A literal syntax for maps: hashmap!{"a" => 1, "b" => 2}
macro_rules! hashmap {
($($k:expr => $v:expr),* $(,)?) => {{ // $(...),* repeats; $(,)? allows a trailing comma
let mut m = HashMap::new();
$( m.insert($k, $v); )*
m
}};
}
// Generate repetitive code: one newtype per ID kind, so a UserId cannot be passed as an OrderId.
macro_rules! id_type {
($($name:ident),+) => {
$(
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct $name(pub u64);
)+
};
}
id_type!(UserId, OrderId);
fn main() {
let ports = hashmap! { "http" => 80, "https" => 443, };
let u = UserId(7);
let o = OrderId(7);
// assert_eq!(u, o); // compile error: different types
println!("{:?} {u:?} {o:?}", ports.get("https"));
}
Procedural macros are Rust programs that run inside the compiler and
take tokens in and produce tokens out. You use them all the time:
#[derive(Debug, Serialize)], #[tokio::main], #[test]. cargo expand
shows what they generate. Write your own only for real boilerplate across a
codebase. They add compile time, and in a dependency they are code that
runs on your machine at build time, which matters for supply-chain
security (Ch 55).