Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Function-like macros

The derive macros you’ve built so far had it easy: the compiler always handed your macro the same thing — the annotated struct or enum, parsed into a DeriveInput. The shape of the input never varied — it was always a type definition — and your job was to generate extra code alongside it.

Function-like macros lift that restriction. They look like a function call with a !:

#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
let s = format!("{name} is {age}");
let q = sqlx::query!("SELECT * FROM users WHERE id = $1", id);
}

“Function-like” describes the call syntax, not how the macro is implemented: vec! and format! are declarative (macro_rules!), while sqlx::query! is procedural — the #[proc_macro] kind this chapter builds. Section 03 draws that line; for now just note they all share the name!( ... ) shape.

That last one even checks its SQL against your database at compile time. Function-like macros can receive any tokens at all between their delimiters — a comma-separated list, a SQL string, even syntax that isn’t valid Rust. Whatever you write inside name! { ... } is handed to your macro as a raw TokenStream, and it’s entirely up to you what it means.

Why is println! a macro?

It’s a fair question — why isn’t println just a function? Two reasons, and both are things a plain function fundamentally cannot do:

  • It’s variadic over mixed types. println!("{} {}", 1, "two") takes a different number of arguments of different types every time it’s called. Rust functions have a fixed arity and fixed parameter types; there’s no way to write fn println(fmt, ...) like in C.
  • It checks the format string at compile time. println!("{}") is a compile error — “1 positional argument in format string, but no arguments were given” — not a runtime panic. To produce that error the macro has to parse the literal "{}" while the program is being compiled and count its placeholders. A function only sees the string’s value at run time, far too late.

Anything that needs a variable shape of input, or needs to inspect its arguments as source code, has to be a macro. println! is both.

Registering a function-like macro

You already know the crate layout from chapter 3 — a proc-macro crate, usually re-exported through a facade crate. The only thing that changes is the attribute on your function:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro] // not #[proc_macro_derive(...)] and not #[proc_macro_attribute]
pub fn my_macro(input: TokenStream) -> TokenStream {
    // `input` is whatever tokens the caller wrote inside `my_macro!( ... )`
    todo!()
}
}

Three kinds of procedural macro, three attributes:

AttributeInvoked asInput it receives
#[proc_macro_derive(Foo)]#[derive(Foo)]the annotated type, as a DeriveInput
#[proc_macro]foo!( ... )the raw tokens between the delimiters
#[proc_macro_attribute]#[foo] itemthe attribute’s args and the item

A function-like macro can be invoked with any of the three delimiters — foo!(...), foo!{...}, or foo![...] — and they’re interchangeable; the macro can’t tell which you used (vec![1, 2, 3] and vec! {1, 2, 3} are the same call). By convention !{ } is used for block-like or statement-like DSLs and !( ) for expression-like calls.

The output is the same TokenStream you’ve been returning all along, and it’s spliced in wherever the macro was called. That means a function-like macro can expand into an expression (let x = foo!();) or, in item position, a set of items (foo!(); at module level) — depending on where it’s invoked and what tokens it returns. (To run several statements in expression position, return a block expression { ... } — you’ll do exactly that in the next section.)

Exercise

The exercise for this section is located in 05_function_like/00_overview

A warm-up from the caller’s side. Use serde_json::json! — a function-like macro that turns a JSON-shaped block of tokens into a serde_json::Value — to build the document the tests expect. You’re using a function-like macro here; from the next section onwards you’ll build your own.