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`")