Declarative and Procedural Macros in Depth
Function-Like Macro Architecture
A function-like procedural macro—declared with #[proc_macro]—is invoked with ! syntax (sql!("SELECT ..."), html! { <div /> }) just like a macro_rules! macro, but executes arbitrary Rust code at compile time over its input TokenStream.
When to Choose #[proc_macro] Over macro_rules!
| Requirement | macro_rules! (Declarative) |
#[proc_macro] (Function-Like Proc Macro) |
|---|---|---|
| Simple pattern substitution & repetition | Ideal (lives in same crate, fast compile time) | Overkill (requires separate proc-macro crate) |
Constructing new identifiers (concat_idents) or case conversion (snake_case $\rightarrow$ PascalCase) |
Limited | Full string manipulation on Ident::to_string() |
| Compile-time validation of string literals (SQL queries, regexes, HTML, format strings) | Cannot inspect characters inside "..." literal |
Can parse and validate string contents at compile time! |
| Stateful compilation / deduplication / complex algorithms | Difficult (requires recursive TT munching) | Standard Rust Vec, HashMap, loops, and structs |
Compile-Time DSL Verification with `#[proc_macro]`:
let route = verified_route!("/users/:id/posts");
|
v [ Runs inside `rustc` at compile time ]
1. Extracts string literal `"/users/:id/posts"`
2. Validates starts with `/` and has valid parameter names
3. Emits pre-parsed static `RouteSpec` struct literal (or compile_error!)
Architecture of a Compile-Time Validating Function-Like Macro
Below is the architecture of a function-like procedural macro verified_route!("/users/:id") that parses and validates route segments at compile time and expands to a zero-cost RouteSpec literal:
// Inside a `proc-macro = true` crate:
/*
use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};
#[proc_macro]
pub fn verified_route(input: TokenStream) -> TokenStream {
let lit = parse_macro_input!(input as LitStr);
let path = lit.value();
if !path.starts_with('/') {
return syn::Error::new(lit.span(), "route path must start with '/'")
.to_compile_error()
.into();
}
let params: Vec<String> = path
.split('/')
.filter_map(|seg| seg.strip_prefix(':'))
.map(str::to_string)
.collect();
quote! {
RouteSpec {
pattern: #lit,
param_names: &[ #( #params ),* ],
}
}
.into()
}
*/
// Runnable demonstration of the compile-time validation logic and generated struct:
#[derive(Debug)]
struct RouteSpec {
pattern: &'static str,
param_names: &'static [&'static str],
}
fn main() {
// Generated at compile time by `verified_route!("/orgs/:org_id/users/:user_id")`:
const USERS_ROUTE: RouteSpec = RouteSpec {
pattern: "/orgs/:org_id/users/:user_id",
param_names: &["org_id", "user_id"],
};
println!("compiled route: {:?}", USERS_ROUTE);
}
compiled route: RouteSpec { pattern: "/orgs/:org_id/users/:user_id", param_names: ["org_id", "user_id"] }