Construct the name for the builder struct, typically by appending ‘Builder’ to the original struct’s name (e.g., Command -> CommandBuilder).

Mục tiêu:


Of course! Let’s get this task done.


Congratulations! You have successfully navigated the most complex part of the analysis phase. With all the necessary information about the original struct’s name, fields, and types neatly stored in your name and collected_fields variables, you are now ready to switch gears. We move from analyzing the user’s code to generating new code.

Our first task in this new phase is to lay the very first stone of our new structure: giving it a name.

From String to Identifier: The Role of format_ident!

Conventionally, a builder for a struct named Command should be called CommandBuilder. Your first thought on how to create this new name might be to use simple string formatting, like format!("{}Builder", name). While this is a logical first step, it produces a standard String.

A procedural macro, however, doesn’t just build strings of code. It builds a TokenStream, which is a structured representation of code. The quote! macro, which we will use extensively from now on, is the tool that helps us build this TokenStream. When quote! needs to insert an identifier (like a struct name, function name, or variable name), it doesn’t want a String; it needs a syn::Ident.

This is a subtle but critical distinction. A syn::Ident is a “syntax tree token” that carries more information than a simple string, including its span, which helps the compiler produce better error messages. Using raw strings can lead to issues with macro hygiene and prevent the generated code from being correctly understood by the compiler.

To solve this, the quote crate provides the perfect tool for the job: the format_ident! macro.

format_ident! works just like the format! macro you already know, but with one key difference: instead of returning a String, it returns a new syn::Ident. This is the correct and idiomatic way to create new identifiers dynamically within a procedural macro.

Let’s update your src/lib.rs file to create the builder’s name.

use proc_macro::TokenStream;
// MODIFIED: We now also import `format_ident` from the `quote` crate.
use quote::{format_ident, quote};
use syn;

// This helper struct remains unchanged.
struct FieldInfo<'a> {
    name: &'a syn::Ident,
    ty: &'a syn::Type,
}

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let ast = syn::parse(input).unwrap();

    // Access the identifier of the struct.
    let name = &ast.ident;

    // Ensure we are deriving on a struct.
    let data = match ast.data {
        syn::Data::Struct(data) => data,
        _ => panic!("Builder can only be derived for structs"),
    };

    // Access the fields of the struct and ensure they are named fields.
    let fields = match data.fields {
        syn::Fields::Named(fields) => fields,
        _ => panic!("Builder can only be derived for structs with named fields"),
    };

    // Initialize a vector to store our collected field information.
    let mut collected_fields = Vec::new();

    // Iterate over the named fields of the struct.
    for field in &fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;

        collected_fields.push(FieldInfo {
            name: field_name,
            ty: field_type,
        });
    }

    // NEW: Construct the identifier for the builder struct.
    // We use the `format_ident!` macro, which is similar to `format!`, but it creates
    // a `syn::Ident` (a syntax tree identifier) instead of a `String`. This is crucial
    // for using the name as a type identifier in the `quote!` macro later.
    // For a struct `Command`, this will create an identifier for `CommandBuilder`.
    let builder_name = format_ident!("{}Builder", name);

    // This is a placeholder for the final code generation step.
    unimplemented!();
}

Code Breakdown

  1. use quote::{format_ident, quote};: We have modified the use statement to explicitly import format_ident! from the quote crate, making it available for use in our function.
  2. let builder_name = format_ident!("{}Builder", name);: This is the new line you’ve added.
    • format_ident!: The macro we are calling to create our new identifier.
    • "{}Builder": The format string. The {} is a placeholder where the first argument will be interpolated.
    • name: The variable containing the &syn::Ident of the original struct (e.g., Command).
    • The result is a new syn::Ident representing CommandBuilder, which is then stored in the builder_name variable.

You have now forged the name of your builder struct. This builder_name variable is a ready-to-use token that you can inject directly into the quote! macro to define your new struct.

What’s Next?

With the builder’s name prepared, you are perfectly positioned for the next task: starting the quote! macro block. You will use this powerful macro to begin generating the actual Rust code for the builder struct definition, using the builder_name identifier you just created like this: pub struct #builder_name { ... }.

Further Reading

Start a quote! macro block to generate the token stream for the builder struct.

Mục tiêu:


Of course! Let’s get this task done.


You’ve done a fantastic job in the analysis phase. You’ve parsed the user’s code, validated it, and meticulously collected all the essential information into the name and collected_fields variables. In the previous task, you forged the identifier for our builder struct, builder_name, using format_ident!. That identifier is the key to our next step.

It’s time to transition from analyzing code to generating it. To do this, we need to introduce the second major tool in our procedural macro arsenal: the quote! macro.

From Data Structures to Code: The Power of quote!

The syn crate is our “deconstructor”—it takes a stream of raw code tokens and breaks it down into structured data (syn::DeriveInput, syn::Field, etc.) that we can analyze. The quote! macro is its perfect counterpart; it is our “constructor.” It takes our structured data and logic and builds a new proc_macro2::TokenStream from it.

quote! provides a feature called quasi-quoting. This is a fancy term for a simple but incredibly powerful idea: you can write code that looks almost exactly like the final Rust code you want to generate, but with special placeholders to inject your variables.

The most important feature of quote! is interpolation. By using a hash symbol (#) before a variable name, you tell quote! to take the value of that variable and insert its token representation directly into the code stream.

For example, if you have a variable my_ident that holds the syn::Ident for MyStruct, the following code:

let my_ident = format_ident!("MyStruct");
let tokens = quote! {
    struct #my_ident;
};

…will produce a TokenStream representing struct MyStruct;. This ability to seamlessly blend static code templates with dynamic variables is the engine that will drive the rest of our macro’s implementation.

Starting the Code Generation

We will now replace the unimplemented!() placeholder at the end of our derive_builder function. Our goal is to:

  1. Start a quote! { ... } block.
  2. Inside this block, define the skeleton of our public builder struct.
  3. Use the #builder_name variable we created in the last step to give our new struct the correct name.
  4. Store the resulting TokenStream in a variable (e.g., output).
  5. Return the output from our function, converting it to the required proc_macro::TokenStream type.

Here is the updated code for src/lib.rs.

use proc_macro::TokenStream;
// We now import `format_ident` from the `quote` crate.
use quote::{format_ident, quote};
use syn;

// This helper struct remains unchanged.
struct FieldInfo<'a> {
    name: &'a syn::Ident,
    ty: &'a syn::Type,
}

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let ast = syn::parse(input).unwrap();

    // Access the identifier of the struct.
    let name = &ast.ident;

    // Ensure we are deriving on a struct.
    let data = match ast.data {
        syn::Data::Struct(data) => data,
        _ => panic!("Builder can only be derived for structs"),
    };

    // Access the fields of the struct and ensure they are named fields.
    let fields = match data.fields {
        syn::Fields::Named(fields) => fields,
        _ => panic!("Builder can only be derived for structs with named fields"),
    };

    // Initialize a vector to store our collected field information.
    let mut collected_fields = Vec::new();

    // Iterate over the named fields of the struct.
    for field in &fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;

        collected_fields.push(FieldInfo {
            name: field_name,
            ty: field_type,
        });
    }

    // Construct the identifier for the builder struct.
    let builder_name = format_ident!("{}Builder", name);

    // NEW: Replace `unimplemented!()` with our code generation logic using `quote!`.
    // The `quote!` macro allows us to write Rust code and interpolate variables into it.
    let output = quote! {
        // We define a new public struct with the name we generated (`CommandBuilder`).
        // The `#builder_name` syntax is the interpolation part of `quote!`. It inserts
        // the `syn::Ident` stored in the `builder_name` variable into the token stream.
        pub struct #builder_name {
            // In the next step, we will generate the fields of the builder here.
        }
    };

    // Convert the `quote!` output into a `proc_macro::TokenStream` and return it.
    // The `.into()` call handles the conversion between the `proc_macro2::TokenStream`
    // that `quote!` produces and the `proc_macro::TokenStream` that the compiler expects.
    output.into()
}

Code Breakdown

  • let output = quote! { ... };: We invoke the quote! macro and assign its output to a new variable, output. The type of output is proc_macro2::TokenStream.
  • pub struct #builder_name { ... }: This is the core of our generated code. It’s a template for a Rust struct definition.
    • The #builder_name part is crucial. It tells quote! to inject the contents of our builder_name variable right here. If the original struct was Command, this generates pub struct CommandBuilder { ... }.
  • output.into(): This is the final and essential step. Procedural macros must return the type proc_macro::TokenStream. The quote! macro, however, produces the more modern and versatile proc_macro2::TokenStream. Luckily, the latter has a .into() method that performs this conversion for us, satisfying the function’s signature.

You have now successfully generated your first piece of code! You’ve created a TokenStream that represents the empty shell of your builder struct.

What’s Next?

With the builder struct’s skeleton in place, the next logical step is to fill it with fields. In the next task, you will modify the quote! block to iterate over your collected_fields vector. For each field in the original struct, you will generate a corresponding field inside the builder, wrapping its type in an Option<T> to signify that it can be set individually.

Further Reading

To become proficient with code generation, a deep understanding of the quote crate is essential.

Generate Builder Struct Fields using quote! Repetition

Mục tiêu: Modify the procedural macro to generate fields for the builder struct. Iterate over the collected field information, wrap each field’s type in Option, and use quote!’s repetition feature to insert the generated field definitions into the builder struct shell.


Excellent! You’ve successfully laid the foundation for our builder by creating its name and the outer struct shell using quote!. You now have a token stream representing an empty struct, like pub struct CommandBuilder {}. This is a significant step, but an empty struct isn’t very useful. It’s time to furnish the interior.

Our next task is to populate this empty shell with fields. For every field in the original struct, we need a corresponding field in our builder. This is where the true power of quote!’s code generation capabilities comes into play.

From a List of Data to a List of Code

You have all the necessary information neatly stored in your collected_fields vector. Each FieldInfo element in that vector contains the name and type of a field from the original struct. Now, we need to translate this list of data into a list of code.

The core principle of the Builder pattern is that each field can be set independently and that the final object is only constructed when all required fields are present. To model this state—where a field might not have been set yet—we will use one of Rust’s most fundamental enums: Option<T>.

For each field field_name: T in the original struct, our builder will have a corresponding field field_name: Option<T>.

  • Initial State: When we create a new builder, every field will be initialized to None.
  • Building Process: As we call setter methods (e.g., .executable("cargo")), the corresponding field will be updated to Some("cargo".to_string()).
  • Finalization: The build() method will later check that all necessary fields are Some before constructing the final object.

Generating Repetitive Code with quote!

Manually writing a line of quote! for each field would be tedious and wouldn’t work for structs with a variable number of fields. quote! has a powerful feature specifically for this: repetition.

The syntax #( ... )* within a quote! block tells it to repeat the code inside the parentheses for each item in an iterable collection. For example, if you have a Vec of identifiers called names, you could generate a series of variable declarations like this:

// let names = vec![ident1, ident2, ident3];
quote! {
    #( let #names = 0; )*
};
// This would expand to:
// let ident1 = 0;
// let ident2 = 0;
// let ident3 = 0;

We will use this exact technique. First, we’ll iterate over our collected_fields vector to create a new vector of TokenStreams, where each token stream represents a single field definition for our builder. Then, we’ll use the repetition syntax to splice this entire vector of definitions into our main quote! block.

Implementing the Field Generation

Let’s modify your derive_builder function. The changes involve creating the field definitions first and then interpolating them into the main quote! macro.

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn;

// This helper struct remains unchanged.
struct FieldInfo<'a> {
    name: &'a syn::Ident,
    ty: &'a syn::Type,
}

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // ... (parsing and data collection logic remains the same)
    let ast = syn::parse(input).unwrap();
    let name = &ast.ident;
    let data = match ast.data {
        syn::Data::Struct(data) => data,
        _ => panic!("Builder can only be derived for structs"),
    };
    let fields = match data.fields {
        syn::Fields::Named(fields) => fields,
        _ => panic!("Builder can only be derived for structs with named fields"),
    };
    let mut collected_fields = Vec::new();
    for field in &fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;
        collected_fields.push(FieldInfo {
            name: field_name,
            ty: field_type,
        });
    }

    let builder_name = format_ident!("{}Builder", name);

    // NEW: Prepare the field definitions for the builder struct.
    // We iterate over our collected `FieldInfo`s and use `quote!` to generate
    // the token stream for each field.
    let builder_fields = collected_fields.iter().map(|field| {
        // Extract the name and type of the field.
        let field_name = field.name;
        let field_type = field.ty;
        // Generate the token stream for a field in the builder struct.
        // The type is wrapped in `Option` to represent that it may not be set yet.
        // e.g., `pub executable: Option<String>,`
        quote! { pub #field_name: Option<#field_type> }
    });

    // Generate the builder struct definition.
    let output = quote! {
        // Define a new public struct with the generated name (e.g., `CommandBuilder`).
        pub struct #builder_name {
            // MODIFIED: Use the repetition syntax `#( ... ),*` to insert the
            // generated field definitions. The `,` ensures the fields are comma-separated.
            // This will expand the `builder_fields` iterator, placing each generated
            // field definition here.
            #(#builder_fields),*
        }
    };

    output.into()
}

Code Breakdown

  1. let builder_fields = collected_fields.iter().map(|field| { ... });

    • We create a new variable builder_fields. This is not a Vec, but an Iterator. Iterators are lazy and efficient, and quote! knows how to consume them directly.
    • collected_fields.iter().map(...) allows us to transform each FieldInfo into something else.
    • Inside the closure, for each field, we extract its name and ty.
    • quote! { pub #field_name: Option<#field_type> }: This is a mini code-generation step. For a field like executable: String, this generates a TokenStream representing pub executable: Option<String>. The result of the map is an iterator that will yield these TokenStreams one by one.
  2. #(#builder_fields),*

    • This is the modified part of your main quote! block.
    • The outer #(...) tells quote! to start a repetition.
    • The inner #builder_fields tells quote! to use the builder_fields iterator as the source of items for the repetition. On each iteration, it takes one TokenStream from the iterator and inserts it.
    • The ,* at the end is the separator. It tells quote! to place a comma (,) after each item, but not after the last one. This is exactly what we need to create a valid, comma-separated list of struct fields.

You have now successfully generated a complete builder struct definition! If the user’s struct was Command { executable: String, args: Vec<String> }, your macro now generates the following code:

pub struct CommandBuilder {
    pub executable: Option<String>,
    pub args: Option<Vec<String>>,
}

What’s Next?

With the builder struct fully defined, our next task is to breathe life into it. A struct definition on its own doesn’t do anything. In the next step, “Implementing Builder Initialization and Field Setters”, you will generate the impl block for this builder, creating the new() function to initialize it and the chainable setter methods that form the core of the builder’s fluent API.

Further Reading

Generate the Builder’s impl Block

Mục tiêu: Extend a Rust procedural macro to generate an empty impl block for the builder struct. This task involves creating the scaffolding for the builder’s methods and combining the generated struct definition with its new implementation block into a single token stream.


Fantastic work! You have successfully generated a complete and correct struct definition for your builder. Your procedural macro now takes a user-defined struct and produces a corresponding Builder struct with all its fields wrapped in Option. This is a huge accomplishment, as you’ve turned a complex set of requirements into tangible, generated code.

However, a struct definition is merely a blueprint for data. On its own, it doesn’t do anything. To make our builder useful, we need to give it behavior—we need to implement methods. This is where Rust’s impl blocks come into play.

From Blueprint to Behavior: The impl Block

In Rust, an impl (implementation) block is how you associate functions and methods with a specific struct, enum, or trait. It’s the keyword that says, “I am now defining the behavior for the following type.”

Our goal is to generate an impl block for our newly created builder struct. This block will serve as the container for all the methods that make the builder pattern work:

  1. A new() function to create a fresh builder instance.
  2. A set of setter methods (e.g., .executable(...), .args(...)) to configure the builder.
  3. A final build() method to construct the target object.

In this task, we will focus on generating the outer shell of this impl block. We’ll create the impl CommandBuilder { ... } structure, preparing the ground for the methods we’ll add in the upcoming tasks.

Combining Generated Code Streams

As your macro grows more complex, you’ll find it beneficial to generate different parts of the code in separate quote! blocks and then combine them at the end. This approach improves readability and makes the code easier to manage.

We will adopt this pattern now:

  1. One quote! block to generate the builder’s struct definition.
  2. A new, separate quote! block to generate the builder’s impl block.
  3. A final quote! block to combine these two token streams into a single output.

Let’s modify your derive_builder function to add this new implementation block. We will build upon the code from the last step.

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn;

// This helper struct remains unchanged.
struct FieldInfo<'a> {
    name: &'a syn::Ident,
    ty: &'a syn::Type,
}

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // Parsing and data collection logic remains exactly the same.
    let ast = syn::parse(input).unwrap();
    let name = &ast.ident;
    let data = match ast.data {
        syn::Data::Struct(data) => data,
        _ => panic!("Builder can only be derived for structs"),
    };
    let fields = match data.fields {
        syn::Fields::Named(fields) => fields,
        _ => panic!("Builder can only be derived for structs with named fields"),
    };
    let mut collected_fields = Vec::new();
    for field in &fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;
        collected_fields.push(FieldInfo {
            name: field_name,
            ty: field_type,
        });
    }

    let builder_name = format_ident!("{}Builder", name);

    let builder_fields = collected_fields.iter().map(|field| {
        let field_name = field.name;
        let field_type = field.ty;
        quote! { pub #field_name: Option<#field_type> }
    });

    // We keep the builder struct definition in its own `quote!` block for clarity.
    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    // NEW: Generate the `impl` block for our builder struct.
    // This block will house the `new()`, setter, and `build()` methods.
    // We use the `#builder_name` identifier again to ensure we're implementing
    // methods on the correct, generated struct.
    let builder_impl = quote! {
        impl #builder_name {
            // In the next tasks, we will add the methods here.
        }
    };

    // NEW: Combine the generated struct and its implementation into a single token stream.
    // By placing them together, `quote!` will output them sequentially, resulting
    // in a valid Rust code file.
    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

Code Breakdown

The changes are focused on organizing our generated code and adding the new impl block:

  1. let builder_struct = quote! { ... };: We’ve taken the struct definition you created in the last task and assigned it to a variable, builder_struct. The logic inside is identical to what you had before.
  2. let builder_impl = quote! { ... };: This is the new piece. We create another token stream containing the impl block.
    • impl #builder_name { ... }: Just as before, we use interpolation (#) to insert the syn::Ident of our builder (e.g., CommandBuilder). This ensures the impl block is correctly associated with the struct we just defined. For now, the body of the impl block is empty.
  3. let output = quote! { #builder_struct #builder_impl };: This is the final assembly step. We create a new quote! block and simply interpolate our previously generated token streams. quote! will seamlessly concatenate them, producing a final stream that contains the struct definition followed immediately by its impl block.

If the user’s struct was Command, your macro now generates the following complete code structure:

pub struct CommandBuilder {
    pub executable: Option<String>,
    pub args: Option<Vec<String>>,
}
impl CommandBuilder {
    // Methods will be implemented here
}

You have successfully created the complete scaffolding for the builder. You have a data structure and a place to define its behavior.

What’s Next?

With the impl block in place, the very first method we need to add is a constructor function—a way to create an instance of our CommandBuilder. In the next task, you will implement the pub fn new() -> Self function inside this impl block, which will initialize a new builder with all its fields set to None.

Further Reading

To solidify your understanding of how behavior is associated with data in Rust, please review the official documentation.

Generate Builder Struct Fields in a Rust Macro

Mục tiêu: Modify a Rust procedural macro to populate a builder struct. This involves iterating over the fields of an existing struct, wrapping each field’s type in an Option, and using the quote! macro’s repetition syntax (#(...)\*) to generate the corresponding fields within the new builder struct definition.


Excellent! You’ve successfully laid the foundation for our builder by creating its name and the outer struct shell using quote!. You now have a token stream representing an empty struct, like pub struct CommandBuilder {}. This is a significant step, but an empty struct isn’t very useful. It’s time to furnish the interior.

Your current task is to populate this empty shell with fields. For every field in the original struct, we need a corresponding field in our builder. This is where the true power of quote!’s code generation capabilities comes into play.

From a List of Data to a List of Code

You have all the necessary information neatly stored in your collected_fields vector. Each FieldInfo element in that vector contains the name and type of a field from the original struct. Now, we need to translate this list of data into a list of code.

The core principle of the Builder pattern is that each field can be set independently and that the final object is only constructed when all required fields are present. To model this state—where a field might not have been set yet—we will use one of Rust’s most fundamental enums: Option<T>.

For each field field_name: T in the original struct, our builder will have a corresponding field field_name: Option<T>.

  • Initial State: When we create a new builder, every field will be initialized to None.
  • Building Process: As we call setter methods (e.g., .executable("cargo")), the corresponding field will be updated to Some("cargo".to_string()).
  • Finalization: The build() method will later check that all necessary fields are Some before constructing the final object.

Generating Repetitive Code with quote!

Manually writing a line of quote! for each field would be tedious and wouldn’t work for structs with a variable number of fields. The quote! macro has a powerful feature specifically for this: repetition.

The syntax #(...)* within a quote! block tells it to repeat the code inside the parentheses for each item in an iterable collection. For example, if you have a Vec of identifiers called names, you could generate a series of variable declarations like this:

// let names = vec![ident1, ident2, ident3];
quote! {
    #( let #names = 0; )*
};
// This would expand to:
// let ident1 = 0;
// let ident2 = 0;
// let ident3 = 0;

We will use this exact technique. First, we’ll iterate over our collected_fields vector to create a new vector of TokenStreams, where each token stream represents a single field definition for our builder. Then, we’ll use the repetition syntax to splice this entire vector of definitions into our main quote! block.

Implementing the Field Generation

Let’s modify your derive_builder function. The changes involve creating the field definitions first and then interpolating them into the main quote! macro.

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn;

// This helper struct remains unchanged.
struct FieldInfo<'a> {
    name: &'a syn::Ident,
    ty: &'a syn::Type,
}

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    // Parsing and data collection logic remains the same.
    let ast = syn::parse(input).unwrap();
    let name = &ast.ident;
    let data = match ast.data {
        syn::Data::Struct(data) => data,
        _ => panic!("Builder can only be derived for structs"),
    };
    let fields = match data.fields {
        syn::Fields::Named(fields) => fields,
        _ => panic!("Builder can only be derived for structs with named fields"),
    };
    let mut collected_fields = Vec::new();
    for field in &fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;
        collected_fields.push(FieldInfo {
            name: field_name,
            ty: field_type,
        });
    }

    let builder_name = format_ident!("{}Builder", name);

    // NEW: Prepare the field definitions for the builder struct.
    // We iterate over our collected `FieldInfo`s and use `quote!` to generate
    // the token stream for each field.
    let builder_fields = collected_fields.iter().map(|field| {
        // Extract the name and type of the field.
        let field_name = field.name;
        let field_type = field.ty;
        // Generate the token stream for a field in the builder struct.
        // The type is wrapped in `Option` to represent that it may not be set yet.
        // e.g., `pub executable: Option<String>,`
        quote! { pub #field_name: Option<#field_type> }
    });

    // Generate the builder struct definition.
    let output = quote! {
        // Define a new public struct with the generated name (e.g., `CommandBuilder`).
        pub struct #builder_name {
            // MODIFIED: Use the repetition syntax `#(...),*` to insert the
            // generated field definitions. The `,` ensures the fields are comma-separated.
            // This will expand the `builder_fields` iterator, placing each generated
            // field definition here.
            #(#builder_fields),*
        }
    };

    output.into()
}

Code Breakdown

  1. let builder_fields = collected_fields.iter().map(|field| { ... });

    • We create a new variable builder_fields. This is not a Vec, but an Iterator. Iterators are lazy and efficient, and quote! knows how to consume them directly.
    • collected_fields.iter().map(...) allows us to transform each FieldInfo into something else.
    • Inside the closure, for each field, we extract its name and ty.
    • quote! { pub #field_name: Option<#field_type> }: This is a mini code-generation step. For a field like executable: String, this generates a TokenStream representing pub executable: Option<String>. The result of the .map() is an iterator that will yield these TokenStreams one by one.
  2. #(#builder_fields),*

    • This is the modified part of your main quote! block where the magic happens.
    • The outer #(...) tells quote! to start a repetition.
    • The inner #builder_fields tells quote! to use the builder_fields iterator as the source of items for the repetition. On each pass, it takes one TokenStream from the iterator and inserts it.
    • The ,* at the end is the separator. It tells quote! to place a comma (,) after each item, but not after the last one. This is exactly what we need to create a valid, comma-separated list of struct fields.

You have now successfully generated a complete builder struct definition! If the user’s struct was Command { executable: String, args: Vec<String> }, your macro now generates the following code:

pub struct CommandBuilder {
    pub executable: Option<String>,
    pub args: Option<Vec<String>>,
}

What’s Next?

With the builder struct fully defined, our next task is to breathe life into it. A struct definition on its own doesn’t do anything. In the next step, “Implementing Builder Initialization and Field Setters”, you will generate the impl block for this builder, creating the new() function to initialize it and the chainable setter methods that form the core of the builder’s fluent API.

Further Reading