Advanced Projects
Procedural Derive Macro
A procedural derive macro (#[proc_macro_derive(Builder, attributes(builder))]) runs at compile timeinside a dedicated crate marked proc-macro = true in Cargo.toml, receiving the annotated type's proc_macro::TokenStream and returning generated Rust tokens (impl blocks, helper structs, trait implementations) spliced directly into the caller's crate.
Two-Crate Workspace Layout for Procedural Macros
workspace/
├── Cargo.toml
├── builder_derive/ <-- [lib] proc-macro = true
│ ├── Cargo.toml
│ └── src/lib.rs <-- Parses TokenStream and emits generated code
└── app/ <-- Binary or library crate consuming #[derive(Builder)]
├── Cargo.toml
└── src/main.rs
# builder_derive/Cargo.toml
[package]
name = "builder_derive"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
| Derive Macro Stage | Input / Output | Purpose |
|---|---|---|
| Token inspection | proc_macro::TokenStream (Ident, Group, Punct, Literal) |
Extracts struct name, visibility, and named fields (name: Type) without external dependencies or via syn |
| Code generation | Formatted Rust source string parsed back into TokenStream (or quote!) |
Generates <Struct>Builder with Option<T> fields, setter methods, and build() -> Result<Struct, &'static str> |
| Hygiene & diagnostics | compile_error!("...") |
Emits clear compiler diagnostics when applied to unsupported items such as unions |
Dependency-Free TokenStream Derive Parser and Generator Engine
Because proc_macro crates run inside the compiler process, the token-parsing and code-generation engine can be tested directly as pure Rust code operating on token trees:
#[derive(Debug, Clone)]
pub struct FieldSpec {
pub name: String,
pub ty: String,
}
#[derive(Debug, Clone)]
pub struct StructSpec {
pub name: String,
pub fields: Vec<FieldSpec>,
}
/// Parses a simplified `struct Name { field1: Type1, field2: Type2 }` definition
/// and synthesizes the complete `<Name>Builder` implementation that a
/// `#[proc_macro_derive(Builder)]` macro emits as a `TokenStream`.
pub fn parse_struct_and_generate_builder(input_tokens: &str) -> Result<String, String> {
let trimmed = input_tokens.trim();
let after_struct = trimmed
.strip_prefix("pub struct ")
.or_else(|| trimmed.strip_prefix("struct "))
.ok_or_else(|| "Builder derive only supports structs".to_string())?;
let open_brace = after_struct
.find('{')
.ok_or_else(|| "expected named-field brace '{'".to_string())?;
let close_brace = after_struct
.rfind('}')
.ok_or_else(|| "expected closing brace '}'".to_string())?;
let struct_name = after_struct[..open_brace].trim().to_string();
let body = &after_struct[open_brace + 1..close_brace];
let mut fields = Vec::new();
for raw_field in body.split(',') {
let f = raw_field.trim();
if f.is_empty() {
continue;
}
let (raw_name, raw_ty) = f
.split_once(':')
.ok_or_else(|| format!("malformed field declaration: '{f}'"))?;
let name = raw_name
.trim()
.trim_start_matches("pub ")
.trim()
.to_string();
let ty = raw_ty.trim().to_string();
fields.push(FieldSpec { name, ty });
}
let builder_name = format!("{struct_name}Builder");
let mut out = String::new();
out.push_str(&format!("#[derive(Default)]\npub struct {builder_name} {{\n"));
for field in &fields {
out.push_str(&format!(" {}: Option<{}>,\n", field.name, field.ty));
}
out.push_str("}\n\n");
out.push_str(&format!("impl {struct_name} {{\n"));
out.push_str(&format!(" pub fn builder() -> {builder_name} {{\n"));
out.push_str(&format!(" {builder_name}::default()\n }}\n}}\n\n"));
out.push_str(&format!("impl {builder_name} {{\n"));
for field in &fields {
out.push_str(&format!(
" pub fn {name}(mut self, value: {ty}) -> Self {{\n self.{name} = Some(value);\n self\n }}\n",
name = field.name,
ty = field.ty
));
}
out.push_str(&format!(
" pub fn build(self) -> Result<{struct_name}, &'static str> {{\n Ok({struct_name} {{\n"
));
for field in &fields {
out.push_str(&format!(
" {name}: self.{name}.ok_or(\"missing required field `{name}`\")?,\n",
name = field.name
));
}
out.push_str(" })\n }\n}");
Ok(out)
}
// Below we include the exact code generated for `ServerConfig` and execute it:
#[derive(Debug, PartialEq)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub max_connections: usize,
}
#[derive(Default)]
pub struct ServerConfigBuilder {
host: Option<String>,
port: Option<u16>,
max_connections: Option<usize>,
}
impl ServerConfig {
pub fn builder() -> ServerConfigBuilder {
ServerConfigBuilder::default()
}
}
impl ServerConfigBuilder {
pub fn host(mut self, value: String) -> Self {
self.host = Some(value);
self
}
pub fn port(mut self, value: u16) -> Self {
self.port = Some(value);
self
}
pub fn max_connections(mut self, value: usize) -> Self {
self.max_connections = Some(value);
self
}
pub fn build(self) -> Result<ServerConfig, &'static str> {
Ok(ServerConfig {
host: self.host.ok_or("missing required field `host`")?,
port: self.port.ok_or("missing required field `port`")?,
max_connections: self
.max_connections
.ok_or("missing required field `max_connections`")?,
})
}
}
fn main() {
let input_item = r#"pub struct ServerConfig {
pub host: String,
pub port: u16,
pub max_connections: usize,
}"#;
let expanded = parse_struct_and_generate_builder(input_item).unwrap();
println!("--- Generated TokenStream Output ---\n{}\n", expanded);
let cfg = ServerConfig::builder()
.host("127.0.0.1".to_string())
.port(8080)
.max_connections(1024)
.build()
.unwrap();
println!("Built struct: {:?}", cfg);
let missing_err = ServerConfig::builder().host("0.0.0.0".to_string()).build();
println!("Incomplete build result: {:?}", missing_err);
}
--- Generated TokenStream Output ---
#[derive(Default)]
pub struct ServerConfigBuilder {
host: Option<String>,
port: Option<u16>,
max_connections: Option<usize>,
}
impl ServerConfig {
pub fn builder() -> ServerConfigBuilder {
ServerConfigBuilder::default()
}
}
impl ServerConfigBuilder {
pub fn host(mut self, value: String) -> Self {
self.host = Some(value);
self
}
pub fn port(mut self, value: u16) -> Self {
self.port = Some(value);
self
}
pub fn max_connections(mut self, value: usize) -> Self {
self.max_connections = Some(value);
self
}
pub fn build(self) -> Result<ServerConfig, &'static str> {
Ok(ServerConfig {
host: self.host.ok_or("missing required field `host`")?,
port: self.port.ok_or("missing required field `port`")?,
max_connections: self.max_connections.ok_or("missing required field `max_connections`")?,
})
}
}
Built struct: ServerConfig { host: "127.0.0.1", port: 8080, max_connections: 1024 }
Incomplete build result: Err("missing required field `port`")