liveBook · Manning

Metaprogramming

As explained in Write Powerful Rust Macros

Metaprogramming is the practice of writing code that treats other code as data so it can manipulate or generate code or add capabilities.

Metaprogramming treats code as data: it manipulates existing code, generates additional code, or adds capabilities. In Rust, macros perform this work at compile time and expand into ordinary structs, functions, and other code before linting, type checking, and compilation. section 1.2

Rust makes metaprogramming a practical everyday technique. A small program can use derive and test attributes, format!, dbg!, assert_eq!, and serialization macros, often hiding substantial generated code behind concise syntax. section 1.1 section 1.3

Choose metaprogramming when ordinary Rust tools are insufficient or when generated code removes meaningful repetition:

  • Use declarative macros first for compact domain-specific generation based on matchers and transcribers. section 1.6 section 3.0
  • Use procedural macros for lower-level token processing, including derive, attribute, and function-like macros. section 1.6 section 3.0
  • Prefer functions and other normal coding tools when they solve the problem; reserve macros for difficult tasks, boilerplate, duplication, or improved application usability. section 1.6 section 1.0

One Rust program, many macro expansions

A Request struct derives Deserialize, format! builds a full name, dbg! prints it, #[test] marks a test, and assert_eq! checks expectations. These concise forms demonstrate how macros surround routine Rust development. section 1.1

Listing 1.1 The program you just wrote
use serde::Deserialize;

#[derive(Deserialize)]           #1
struct Request {
  given_name: String,
  last_name: String,
}

fn full_name(given: &str, last: &str) -> String {
  format!("{} {}", given, last)       #2
}

fn main() {
  let r = Request {
     given_name: "Sam".to_string(),
     last_name: "Hall".to_string()
  };
  dbg!(full_name(&r.given_name, &r.last_name));    
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_deserialize() {
    let actual: Request =
        serde_json::from_str("{ \"given_name\\": \\"Test\\",
     \"last_name\\": \\"McTest\\" }")
            .expect("deserialize to work");

    assert_eq!(actual.given_name, "Test".to_string());    
    assert_eq!(actual.last_name, "McTest".to_string());   
  }
}

Inspect the complete Rust example showing serialization, a normal function beside macros, debugging, and a deserialization test. section 1.1

How Write Powerful Rust Macros covers it

How deep it goes
Defines code generation and compile-time expansion, then distinguishes declarative and procedural macros without implementing either internally.
Examples
Uses Deserialize, format!, dbg!, test attributes, assert_eq!, and generated code hidden within a small Rust program.
What it assumes
Assumes Rust syntax familiarity while connecting macro work with testing, debugging, libraries, compilation, types, and parsing.
What you can do after
Evaluate/understand when Rust macros provide capabilities that functions and ordinary coding tools cannot provide.
How it connects
Connects metaprogramming with macros, compile-time checking, hygiene, serialization, testing, parsing, and code generation.

Also explained in

Compare how 3 books explain metaprogramming →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.