Rust macros are a powerful feature that allows developers to extend the language's syntax and automate repetitive code patterns. They enable metaprogramming, which is the ability to write code that generates other code.
Macros in Rust are a way to write code that writes other code, often referred to as "metaprogramming". They come in two main flavors:
Declarative macros, also known as "macro_rules!" macros, allow you to write something similar to a match expression. They compare literal Rust syntax to patterns and replace the code with other Rust code based on the match.
macro_rules! say_hello {
// Match () and expand to "Hello!"
() => {
println!("Hello!");
};
// Match a single name and expand to "Hello, {name}!"
($name:expr) => {
println!("Hello, {}!", $name);
};
}
fn main() {
say_hello!(); // Prints: Hello!
say_hello!("Rust"); // Prints: Hello, Rust!
}
Procedural macros are more powerful but also more complex. They operate on the abstract syntax tree (AST) of Rust code and can generate arbitrary Rust code as their output. There are three types of procedural macros:
Here's a simple example of a custom derive macro:
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
Macros are useful in several scenarios:
To fully understand and utilize Rust macros, it's helpful to be familiar with these related concepts:
Mastering Rust macros can significantly enhance your ability to write efficient, reusable, and expressive code. While they require careful use, macros are an essential tool in the Rust programmer's toolkit for metaprogramming and code generation.