Macros
On this page 38
Home's macro system provides powerful metaprogramming capabilities, enabling code generation, domain-specific languages, and compile-time computation. Macros operate on the abstract syntax tree, providing type-aware transformations.
Overview
Home macros offer:
- Declarative macros: Pattern-based code generation
- Procedural macros: Full programmatic AST manipulation
- Derive macros: Automatic trait implementation generation
- Attribute macros: Transform items with custom attributes
- Hygiene: Prevent accidental name capture and conflicts
Declarative Macros
Basic Syntax
Declarative macros match patterns and expand to code:
macro vec($($element:expr),* $(,)?) {
{
let mut temp = Vec.new()
$(temp.push($element);)*
temp
}
}
// Usage
let numbers = vec![1, 2, 3, 4, 5]
let strings = vec!["a", "b", "c"]
Pattern Matching in Macros
macro match*type($value:expr) {
match $value {
* if std.type*name::<typeof($value)>() == "i32" => "integer",
* if std.type*name::<typeof($value)>() == "string" => "string",
* => "unknown",
}
}
Repetition Patterns
// Zero or more (*)
macro println($fmt:literal $(, $arg:expr)*) {
print($fmt $(, $arg)*)
print("\n")
}
// One or more (+)
macro min($first:expr $(, $rest:expr)+) {
{
let mut result = $first
$(
if $rest < result {
result = $rest
}
)+
result
}
}
// Zero or one (?)
macro optional*init($name:ident: $type:ty $(= $default:expr)?) {
let $name: $type = $($default)?
}
Fragment Types
macro demonstrate*fragments(
$ident:ident, // Identifier
$expr:expr, // Expression
$ty:ty, // Type
$pat:pat, // Pattern
$stmt:stmt, // Statement
$block:block, // Block expression
$item:item, // Item (fn, struct, etc.)
$path:path, // Type path
$literal:literal, // Literal value
$lifetime:lifetime, // Lifetime
$meta:meta, // Attribute content
) {
// ... expansion
}
Recursive Macros
macro count($($element:tt)*) {
0 $(+ count!(@single $element))*
}
macro count(@single $element:tt) {
1
}
// Usage
let n = count!(a b c d e) // 5
Procedural Macros
Function-like Procedural Macros
# [proc*macro]
fn sql(input: TokenStream) -> TokenStream {
let query = parse*sql(input)
let validated = validate*query(query)
generate*query*code(validated)
}
// Usage
let users = sql!(SELECT * FROM users WHERE age > 18)
Implementing Procedural Macros
use home.proc*macro.{TokenStream, TokenTree, Literal, Ident, Punct}
# [proc*macro]
fn json(input: TokenStream) -> TokenStream {
let parsed = parse*json*tokens(input)
let mut output = TokenStream.new()
output.extend(quote! {
JsonValue.Object(HashMap.from([
#(#parsed),*
]))
})
output
}
Derive Macros
Basic Derive
# [derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
// Automatically implements:
// - Debug: formatted debug output
// - Clone: value copying
// - PartialEq: equality comparison
Custom Derive Macros
# [proc*macro*derive(Serialize)]
fn derive*serialize(input: TokenStream) -> TokenStream {
let ast = parse*derive*input(input)
let name = ast.ident
let fields = get*fields(ast)
quote! {
impl Serialize for #name {
fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> {
serializer.begin*object()?
#(
serializer.field(stringify!(#fields), &self.#fields)?
)*
serializer.end*object()
}
}
}
}
Derive with Attributes
# [proc*macro*derive(Serialize, attributes(serde))]
fn derive*serialize*with*attrs(input: TokenStream) -> TokenStream {
// Can now process #[serde(...)] attributes on fields
}
// Usage
# [derive(Serialize)]
struct User {
#[serde(rename = "user*name")]
name: string,
#[serde(skip)]
password*hash: string,
#[serde(default)]
active: bool,
}
Attribute Macros
Basic Attribute Macros
# [proc*macro*attribute]
fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
let route*path = parse*route*path(attr)
let function = parse*fn(item)
quote! {
#[doc = concat!("Route: ", #route*path)]
#function
inventory.submit! {
Route {
path: #route*path,
handler: #function.name,
}
}
}
}
// Usage
# [route("/api/users")]
fn get*users() -> Response {
// ...
}
Transforming Items
# [proc*macro*attribute]
fn async*trait(*attr: TokenStream, item: TokenStream) -> TokenStream {
let trait*def = parse*trait(item)
// Transform async fn to return BoxFuture
let transformed = trait*def.methods.map(|method| {
if method.is*async {
transform*async*method(method)
} else {
method
}
})
quote! {
trait #trait*def.name {
#(#transformed)*
}
}
}
Macro Hygiene
Hygienic Identifiers
macro create*var() {
let x = 42 // This 'x' won't conflict with outer 'x'
}
let x = 10
create*var!()
print(x) // Still 10, macro's x is separate
Breaking Hygiene When Needed
macro declare($name:ident, $value:expr) {
let $name = $value // $name escapes hygiene
}
declare!(answer, 42)
print(answer) // 42 - accessible because we used the caller's identifier
Span Manipulation
# [proc*macro]
fn with*span(input: TokenStream) -> TokenStream {
let span = input.span() // Preserve source location
quote*spanned! { span =>
// Generated code points to original location for errors
compile*error!("Something went wrong")
}
}
Built-in Macros
Compile-Time Assertions
// Static assertion
static*assert!(size*of::<i32>() == 4)
static*assert!(align*of::<u64>() == 8)
// Const evaluation
const VALUE: i32 = const*eval!(factorial(10))
Debug and Inspection
// Print expression and value
let x = 5
dbg!(x * 2) // Prints: [file:line] x * 2 = 10
// Get type name
let name = type*name!(Vec<i32>) // "Vec<i32>"
// Get file/line/column
let location = source*location!() // "src/main.home:42:5"
Conditional Compilation
macro cfg($condition:meta) {
// Evaluates condition at compile time
}
# [cfg(target*os = "linux")]
fn platform*specific() {
// Only compiled on Linux
}
let value = cfg!(debug*mode) ? "debug" : "release"
Domain-Specific Languages
SQL DSL
macro sql($($tokens:tt)*) {
parse*and*validate*sql!($($tokens)*)
}
let query = sql! {
SELECT name, email
FROM users
WHERE active = true
ORDER BY created*at DESC
LIMIT 10
}
HTML DSL
macro html($($tokens:tt)*) {
parse*html!($($tokens)*)
}
let page = html! {
<div class="container">
<h1>{title}</h1>
<p>{content}</p>
<ul>
{for item in items {
<li>{item}</li>
}}
</ul>
</div>
}
Regex DSL
macro regex($pattern:literal) {
// Compile-time regex validation
compile*regex!($pattern)
}
let email*pattern = regex!(r"^[a-zA-Z0-9.*%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}quot;)
Advanced Techniques
Token Tree Munching
macro parse*list($($tokens:tt)*) {
parse*list*impl!([] $($tokens)*)
}
macro parse*list*impl([$($acc:expr),*] $head:expr, $($rest:tt)*) {
parse*list*impl!([$($acc,)* $head] $($rest)*)
}
macro parse*list*impl([$($acc:expr),*] $last:expr) {
[$($acc,)* $last]
}
macro parse*list*impl([$($acc:expr),*]) {
[$($acc),*]
}
Push-Down Accumulation
macro reverse($($items:expr),*) {
reverse*impl!([] $($items),*)
}
macro reverse*impl([$($acc:expr),*] $head:expr $(, $rest:expr)*) {
reverse*impl!([$head $(, $acc)*] $($rest),*)
}
macro reverse*impl([$($acc:expr),*]) {
($($acc),*)
}
let reversed = reverse!(1, 2, 3, 4, 5) // (5, 4, 3, 2, 1)
Callback Pattern
macro with*each($callback:ident, $($items:expr),*) {
$(
$callback!($items);
)*
}
macro print*item($item:expr) {
print("{:?}", $item)
}
with*each!(print*item, 1, 2, 3)
Edge Cases
Macro Ordering
// Macros must be defined before use in the same module
// Or imported from another module
// This works:
macro first() { 1 }
let a = first!()
// Forward references require imports:
use other*module.later*macro
let b = later*macro!()
Ambiguous Syntax
// Use parentheses to disambiguate
macro ambiguous($e:expr) {
($e) + 1 // Parentheses ensure correct parsing
}
// Token trees for maximum flexibility
macro flexible($($tt:tt)*) {
// Can handle any syntax
}
Recursive Expansion Limits
// Home has a recursion limit (default 128)
# ![recursion*limit = "256"]
macro deeply*recursive($n:expr) {
// ... deep recursion ...
}
Best Practices
-
Prefer functions over macros when possible:
// Use function fn add(a: i32, b: i32) -> i32 { a + b } // Use macro only when needed (variadic, syntax extension, etc.) macro sum($($n:expr),+) { 0 $(+ $n)+ } -
Document macro syntax clearly:
/// Creates a HashMap with the given key-value pairs. /// /// # Syntax /// ``` /// map! { key1 => value1, key2 => value2 } /// ``` macro map($($key:expr => $value:expr),* $(,)?) { // ... } -
Provide helpful error messages:
macro require*even($n:expr) { const *: () = { if $n % 2 != 0 { compile*error!(concat!(stringify!($n), " must be even")) } }; } -
Test macro edge cases:
#[test] fn test*vec*macro() { assert*eq!(vec![], Vec::<i32>.new()) assert*eq!(vec![1], vec![1]) assert*eq!(vec![1,], vec![1]) // Trailing comma assert_eq!(vec![1, 2, 3], vec![1, 2, 3]) } -
Use appropriate macro delimiters:
// Parentheses for function-like macros println!("hello") // Braces for block-like macros html! { <div>content</div> } // Brackets for collection-like macros vec![1, 2, 3]