Project 10: Procedural Macro Library (Compile-Time Code Generation)
A derive macro that auto-generates code at compile time—like implementing
#[derive(Builder)]that creates builder patterns, or#[derive(Serialize)]that creates serialization code.
Quick Reference
| Attribute | Value |
|---|---|
| Primary Language | Rust |
| Alternative Languages | None (Rust-specific feature) |
| Difficulty | Level 4: Expert |
| Time Estimate | 2-3 weeks |
| Knowledge Area | Metaprogramming / Macros / Compiler Integration |
| Tooling | syn, quote, proc-macro2 |
| Prerequisites | Most prior projects completed, strong Rust fundamentals |
What You Will Build
A derive macro that auto-generates code at compile time—like implementing #[derive(Builder)] that creates builder patterns, or #[derive(Serialize)] that creates serialization code.
Why It Matters
This project builds core skills that appear repeatedly in real-world systems and tooling.
Core Challenges
- Parsing Rust syntax with syn → maps to understanding TokenStream and AST
- Generating valid Rust code with quote → maps to code generation
- Handling edge cases and error reporting → maps to compile-time diagnostics
- Testing macro output → maps to macro debugging techniques
Key Concepts
- Procedural macros: “The Rust Programming Language” Chapter 19 - Steve Klabnik
- TokenStream manipulation: “Rust for Rustaceans” Chapter 9 - Jon Gjengset
- syn crate for parsing: syn documentation
- quote crate for generation: quote documentation
Real-World Outcome
// User writes this:
#[derive(Builder)]
struct Config {
host: String,
port: u16,
#[builder(default = "false")]
debug: bool,
#[builder(optional)]
timeout: Option<u64>,
}
// Your macro generates this at compile time:
impl Config {
fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
}
struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
debug: bool,
timeout: Option<u64>,
}
impl ConfigBuilder {
fn host(mut self, value: String) -> Self {
self.host = Some(value);
self
}
fn port(mut self, value: u16) -> Self {
self.port = Some(value);
self
}
fn debug(mut self, value: bool) -> Self {
self.debug = value;
self
}
fn timeout(mut self, value: u64) -> Self {
self.timeout = Some(value);
self
}
fn build(self) -> Result<Config, &'static str> {
Ok(Config {
host: self.host.ok_or("host is required")?,
port: self.port.ok_or("port is required")?,
debug: self.debug,
timeout: self.timeout,
})
}
}
// Usage:
fn main() {
let config = Config::builder()
.host("localhost".into())
.port(8080)
.build()
.unwrap();
}
Implementation Guide
- Reproduce the simplest happy-path scenario.
- Build the smallest working version of the core feature.
- Add input validation and error handling.
- Add instrumentation/logging to confirm behavior.
- Refactor into clean modules with tests.
Milestones
- Milestone 1: Minimal working program that runs end-to-end.
- Milestone 2: Correct outputs for typical inputs.
- Milestone 3: Robust handling of edge cases.
- Milestone 4: Clean structure and documented usage.
Validation Checklist
- Output matches the real-world outcome example
- Handles invalid inputs safely
- Provides clear errors and exit codes
- Repeatable results across runs
References
- Main guide:
LEARN_RUST_DEEP_DIVE.md - “The Rust Programming Language” by Steve Klabnik (Chapter 19) + “Rust for Rustaceans”