Macros
Rust macros generate code. You can recognize many of them by the ! after
their name:
#![allow(unused)]
fn main() {
println!("hello");
vec![1, 2, 3];
panic!("something went wrong");
}
A macro can accept syntax that an ordinary function cannot. For example,
println! accepts a format string followed by any number of values. The macro
checks the formatting arguments and expands into the code needed to print them.
This chapter uses macros without examining how to write them.
println! prints formatted output
println! writes a line to standard output:
fn main() {
let name = "Ferris";
let level = 3;
println!("Hello, {name}");
println!("level: {level}");
println!("next level: {}", level + 1);
}
The output is:
Hello, Ferris
level: 3
next level: 4
Named variables can appear directly inside braces. An empty {} uses the next
argument after the format string.
The value must implement the formatting trait requested by the placeholder:
#![allow(unused)]
fn main() {
let value = 42;
println!("{}", value); // Display
println!("{:?}", value); // Debug
println!("{value:#?}"); // pretty-printed Debug
}
Display is intended for user-facing output. Debug is intended for
developers and diagnostics. Many standard types implement both, but your own
types do not receive either implementation automatically.
#[derive(...)] generates trait implementations
The derive attribute asks Rust to generate implementations of selected traits
for a struct or enum:
#[derive(Debug, Clone, PartialEq)]
struct User {
name: String,
level: u32,
}
fn main() {
let user = User {
name: String::from("Ferris"),
level: 3,
};
let copy = user.clone();
println!("{user:?}");
println!("same value: {}", user == copy);
}
Each name inside derive adds a capability:
Debugenables diagnostic formatting with{:?}.Cloneenables an explicit.clone().Copyallows implicit bitwise copying instead of moves.PartialEqenables==and!=.Eqstates that equality is fully reflexive.Defaultprovides a default value when every field supports it.
Deriving a trait works only when all relevant fields implement that trait. A
struct containing a field that is not Clone, for example, cannot derive
Clone.
derive is a macro even though it does not use trailing ! syntax. It is
invoked through an attribute placed above the item it affects.
dbg! shows an expression and its value
dbg! is a quick way to inspect a value while developing:
fn main() {
let width = 4;
let height = 6;
let area = dbg!(width * height);
println!("area: {area}");
}
The exact location depends on the source file, but the diagnostic resembles:
[src/main.rs:4:16] width * height = 24
area: 24
dbg! differs from println! in several useful ways:
- it prints the source expression, file, and line number;
- it uses
Debugformatting; - it writes to standard error rather than standard output;
- it returns the value it receives.
Because it returns the value, dbg! can wrap an expression without otherwise
changing the surrounding code:
#![allow(unused)]
fn main() {
let subtotal = 20;
let tax = 2;
let total = dbg!(subtotal + tax);
}
dbg! takes ownership unless you borrow
Macros still follow ordinary ownership rules:
fn main() {
let name = String::from("Ferris");
dbg!(&name);
println!("{name}");
}
Passing &name lets dbg! inspect a reference. Passing name directly would
move the String into the macro’s expression, so the binding could not be used
afterward.
This is not special behavior built into dbg!. It follows from the same move
and borrowing rules as ordinary Rust code.
When to use each tool
Use println! when output is part of the program’s behavior or when you want
controlled formatting. Use dbg! for quick, temporary inspection during
development. Use #[derive(...)] to generate standard trait behavior instead
of writing repetitive implementations by hand.
In larger applications, structured logging usually replaces temporary
println! and dbg! calls. These two macros remain ideal for small programs
and quick experiments.