You now understand both macro kinds. This lesson grounds them in the macros you'll actually meet — the ones that quietly run your Rust programs.
The declarative macros you use daily
println!, format!, vec!, assert!, assert_eq!, panic! — all macro_rules! macros from the standard library. They exist as macros (not functions) precisely because they need variadic arguments or compile-time format checking. Every time you typed a ! in this quest, you invoked one.
The procedural macros that power the ecosystem
#[derive(Serialize, Deserialize)] from serde generates an entire serialization layer from your struct definition — arguably the most-used procedural macro in Rust. #[tokio::main] bootstraps the async runtime. #[derive(thiserror::Error)] generated your error boilerplate back in the Error Handling track. These macros are why Rust feels high-level despite being a systems language.
The takeaway
You don't need to write macros to be a productive Rust programmer — you need to read them: recognize what #[derive(...)] generates, know that a ! means compile-time expansion, and trust the well-tested ecosystem macros. Write your own only when boilerplate genuinely can't be solved by a function or generic. That judgment — function first, macro last — is the mark of mature Rust.