You've used macros since lesson one — println!, vec!, format! all end in !. This track demystifies what that ! means and when you'd write your own.
Macros are code that writes code
A macro runs at compile time and generates Rust source before the real compilation happens. That's fundamentally different from a function, which runs at runtime on values. A macro operates on the syntax itself — it sees tokens and produces tokens. The ! is how you tell a macro invocation from a function call.
What macros can do that functions can't
Three things. Variadic arguments: println! takes any number of arguments — a function can't. Arbitrary syntax: vec![1, 2, 3] and vec![0; 100] use syntax no function signature could express. Compile-time generation: #[derive(Debug)] writes an entire trait impl for you. Functions are runtime values; macros are compile-time code generators.
Use them sparingly
Macros are powerful but harder to read, write, and debug than functions. The Rust community's guidance is clear: reach for a function first, and only write a macro when a function genuinely can't express what you need. Most code you write will have zero custom macros — and that's correct.