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

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:

  • Debug enables diagnostic formatting with {:?}.
  • Clone enables an explicit .clone().
  • Copy allows implicit bitwise copying instead of moves.
  • PartialEq enables == and !=.
  • Eq states that equality is fully reflexive.
  • Default provides 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 Debug formatting;
  • 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.