What is a macro in Rust and how do declarative and procedural macros differ?
Learn what Rust macros are and how declarative macro_rules! differs from procedural macros, with clear examples, benefits, and interview-ready answers.
Expected Interview Answer
A Rust macro is code that writes code: it runs at compile time and expands into ordinary Rust before type-checking, letting you avoid boilerplate and build custom syntax. Declarative macros match patterns with macro_rules!, while procedural macros are functions that transform token streams.
Declarative macros (macro_rules!) work like a match statement over syntax fragments — you write patterns and the templates they expand into, which is great for lightweight repetition such as vec!. Procedural macros are compiled crate-local functions that receive a TokenStream and return one; they come in three kinds — custom derive (#[derive(...)]), attribute-like (#[route(...)]), and function-like (sql!(...)). Procedural macros are far more powerful because they can inspect and generate arbitrary code, but they require a separate proc-macro crate and typically pull in syn and quote.
- Eliminates repetitive boilerplate at compile time
- Macros are hygienic, avoiding accidental variable capture
- Zero runtime cost — expansion happens before compilation finishes
- Enables custom derives like Serialize and Debug
- Lets libraries offer expressive, domain-specific syntax
AI Mentor Explanation
Think of a coach's shorthand on a whiteboard: writing 'run standard powerplay drill' expands into the full sequence of fielding positions and bowling changes every player already knows. A declarative macro is that fixed shorthand matching a known drill name to a known layout, while a procedural macro is an analyst who studies live match data and generates a brand-new custom drill on the spot.
Step-by-Step Explanation
Step 1
Recognize the need
Spot repeated code or syntax that plain functions and generics cannot abstract away, such as variadic constructors or trait derivation.
Step 2
Choose the macro kind
Pick macro_rules! for simple pattern-based repetition, or a procedural macro when you must inspect or generate arbitrary code.
Step 3
Write declarative rules
Define macro_rules! arms that match token fragments like $x:expr and expand into templated Rust code.
Step 4
Build procedural crates
For proc macros, create a proc-macro = true crate and parse the input TokenStream with syn, then emit output with quote.
Step 5
Expand and verify
Use cargo expand to see the generated code and confirm hygiene and correctness before relying on it.
What Interviewer Expects
- Clear definition of compile-time code generation
- The macro_rules! versus procedural distinction
- Naming the three procedural macro kinds
- Awareness of macro hygiene
- Mentioning syn and quote for proc macros
Common Mistakes
- Confusing macros with runtime functions
- Thinking macros have a runtime performance cost
- Forgetting procedural macros need their own crate
- Not knowing the three proc-macro kinds
- Ignoring hygiene and variable capture concerns
Best Answer (HR Friendly)
“A macro in Rust is code that generates other code automatically when the program is built, saving developers from writing the same thing over and over. There are two families: simple pattern-based ones and more powerful ones that can read and transform the code itself.”
Code Example
macro_rules! my_vec {
( $( $x:expr ),* ) => {{
let mut v = Vec::new();
$( v.push($x); )*
v
}};
}
fn main() {
let nums = my_vec![1, 2, 3];
println!("{:?}", nums);
}use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloWorld)]
pub fn hello_world(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let expanded = quote! {
impl #name {
fn hello() { println!("Hello from {}", stringify!(#name)); }
}
};
expanded.into()
}Follow-up Questions
- What does macro hygiene protect against?
- Why do procedural macros require a separate crate?
- What roles do the syn and quote crates play?
- How does cargo expand help debug macros?
- When would you prefer generics over a macro?
MCQ Practice
1. Which macro type is defined using macro_rules!?
macro_rules! defines declarative macros that expand by matching syntactic patterns.
2. Which is NOT one of the three procedural macro kinds?
The three procedural kinds are custom derive, attribute-like, and function-like; 'recursive-like' is not a category.
3. When do Rust macros execute?
Macros are expanded at compile time before type checking, so they add no runtime overhead.
Flash Cards
What is a Rust macro? — Code that generates code at compile time, expanding into ordinary Rust before type checking.
macro_rules! creates which kind? — Declarative macros that expand by matching syntactic patterns.
Three procedural macro kinds? — Custom derive, attribute-like, and function-like.
Which crates power proc macros? — syn to parse the TokenStream and quote to generate output.
What is macro hygiene? — The guarantee that macro-introduced identifiers do not accidentally clash with the caller's variables.