Generate the Builder’s impl Block

Mục tiêu: Extend the Rust procedural macro to generate an empty impl block for the builder struct. This involves organizing the generated code into separate token streams for the struct definition and its implementation, then combining them.


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.

Implement a pub fn new() -> Self function.

Mục tiêu:


Of course! Let’s get this task done.


You’ve done an excellent job setting up the complete scaffolding for our builder. In the previous task, you generated not only the struct definition but also the empty impl block that will house its behavior. You now have a perfect container, waiting to be filled with the logic that makes the builder pattern so powerful.

The very first piece of logic any builder needs is a way to be created. In Rust, the idiomatic way to provide a constructor is by creating a public associated function, conventionally named new(). This function will be the entry point for a user to start the building process, like Command::builder(), which will in turn call CommandBuilder::new().

Associated Functions vs. Methods

Before we dive into the code, it’s important to understand a key distinction in Rust: * A method is a function that takes an instance of the object as its first parameter, usually self, &self, or &mut self. Methods are called using dot notation (e.g., my_instance.my_method()). * An associated function is a function that is part of a type’s impl block but does not take self as its first parameter. They are often used for constructors that create an instance of the type itself. They are called using the :: syntax (e.g., String::new(), CommandBuilder::new()).

Our new() function will be an associated function because it needs to create a CommandBuilder from scratch, not operate on an existing one.

The Self Keyword and the Initial State

The function signature we will generate is pub fn new() -> Self. Let’s break this down: * pub fn new(): A public function named new that takes no arguments. * -> Self: This is the return type. Inside an impl block, the special keyword Self (with a capital ‘S’) is an alias for the type the block is implementing. So, inside impl CommandBuilder { ... }, Self is simply a shorthand for CommandBuilder. Using Self is considered a best practice as it makes the code more resilient to renaming.

The purpose of new() is to return a builder in its default, initial state. For our pattern, the initial state is one where no fields have been set yet. Since every field in our builder is an Option<T>, this means the initial value for every single field must be None.

Generating the new() Function

To generate the new() function’s body, we will once again use the powerful repetition feature of the quote! macro. We will iterate over our collected_fields to generate a list of initializers (field_name: None) and then splice them into the struct construction syntax.

Here is the updated code for src/lib.rs. The changes are focused inside the builder_impl block.

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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    // MODIFIED: We are now adding the `new()` function to the builder's `impl` block.
    let builder_impl = quote! {
        impl #builder_name {
            // Define the public `new()` associated function.
            // It returns an instance of the builder itself, denoted by `Self`.
            pub fn new() -> Self {
                // Construct an instance of `Self` (the builder).
                Self {
                    // For each field in the original struct, we initialize the
                    // corresponding builder field to `None`, as no values have been provided yet.
                    // We achieve this by iterating over our collected fields and generating
                    // the line `#field_name: None` for each one.
                    #( #collected_fields.name: None ),*
                }
            }
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

Code Breakdown

The key change is the new content inside the builder_impl block. Let’s look closer at the struct construction:

Self {
    #( #collected_fields.name: None ),*
}
  • Self { ... }: This is the standard Rust syntax for constructing a struct. Here, we are constructing our builder.
  • #( ... ),*: This is the quote! repetition syntax. It will repeat the pattern inside the parentheses for each element in the collection that follows, separating each repetition with a comma (,).
  • #collected_fields.name: This is a new and powerful feature of quote!’s repetition. When the collection you provide (collected_fields) is a struct, you can directly access its fields within the repetition pattern. Here, for each FieldInfo in collected_fields, we are accessing its name field.
  • : None: This is the static part of our pattern.

For a struct Command { executable: String, args: Vec<String> }, this block expands to:

Self {
    executable: None,
    args: None,
}

This generates a perfect, fully-functional new() method that correctly initializes our builder.

What’s Next?

You have now given users a way to create a builder instance. This is a crucial step, but the builder is still inert. The next logical step, and the core of the builder’s “fluent API,” is to create the setter methods. In the upcoming tasks, you will generate a public setter method for each field (e.g., pub fn executable(&mut self, ...)), which will allow users to configure the builder’s values one by one.

Further Reading

Implement the Builder’s new() Method

Mục tiêu: Implement the body of the new() function for a procedural macro builder. Use the quote! macro’s repetition feature to dynamically iterate over the struct’s fields and initialize each one to None, creating the builder’s default state.


Excellent! You have successfully defined the pub fn new() -> Self function signature within the builder’s impl block. This creates the public entry point for users to start constructing an object. Now, your current task is to implement the body of this function—to breathe life into it and make it return a valid, initialized builder instance.

The Initial State of a Builder

The core purpose of the new() function is to provide a “blank slate” builder. In the context of our design, a blank slate means that no fields have been set yet. Since every field in our builder struct is of type Option<T>, the natural and correct way to represent this “unset” state is with the None value.

Therefore, the new() function must construct an instance of the builder (Self) and initialize every single one of its fields to None.

Generating Dynamic Initializers

We cannot hardcode the field initializations because our macro must work for any struct with any number of fields. This is where the data we meticulously gathered into the collected_fields vector becomes invaluable. We need to iterate over this collection and, for each field, generate a line of code that looks like field_name: None.

The quote! macro’s repetition feature is the perfect tool for this job. We will:

  1. Create an iterator that yields just the names of the fields from our collected_fields vector.
  2. Use this iterator within the quote! macro to generate the comma-separated list of initializers.

Let’s update the builder_impl block in your src/lib.rs to fill in the body of the new() function.

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

// 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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    // NEW: Extract just the field names into an iterator for the `new()` method.
    // This makes the code in the `quote!` block cleaner and more explicit.
    let field_names = collected_fields.iter().map(|field| field.name);

    let builder_impl = quote! {
        impl #builder_name {
            pub fn new() -> Self {
                // MODIFIED: We now construct an instance of `Self` (the builder).
                Self {
                    // Use `quote!`'s repetition syntax to initialize each field to `None`.
                    // The `#(#field_names: None),*` pattern iterates through the `field_names`
                    // iterator. For each name, it generates `the_field_name: None,`.
                    // `quote!` is smart enough to handle the trailing comma.
                    #(#field_names: None),*
                }
            }
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

Code Breakdown

The change is small but powerful. Let’s analyze it in detail.

  1. let field_names = collected_fields.iter().map(|field| field.name);

    • Before the main quote! block, we create a new variable field_names.
    • collected_fields.iter() creates an iterator over our Vec<FieldInfo>.
    • .map(|field| field.name) is a transformative step. It takes each FieldInfo struct from the iterator and returns only its name field.
    • The result, field_names, is now an iterator that will yield &syn::Idents—exactly what we need for the initialization keys.
  2. Self { #(#field_names: None),* }

    • Self { ... }: This is the standard Rust syntax for constructing a struct. Inside an impl block, Self is an alias for the type being implemented (e.g., CommandBuilder).
    • #(#field_names: None),*: This is the repetition magic.
      • The outer #(...) signals the start of a repetition block.
      • The inner #field_names tells quote! to use our field_names iterator as the source.
      • : None is the static code that will be repeated for each item.
      • The trailing ,* is the separator, telling quote! to place a comma between each generated line.

For a user’s struct like Command { executable: String, args: Vec<String> }, your macro now generates the following complete new() function:

impl CommandBuilder {
    pub fn new() -> Self {
        Self {
            executable: None,
            args: None,
        }
    }
}

This is a perfect, fully-functional constructor for our builder.

What’s Next?

You have now given users a way to create an empty builder instance. This is a crucial step, but the builder is still inert; there’s no way to set the values. The next logical step, and the core of the builder’s “fluent API,” is to create the setter methods. In the upcoming tasks, you will generate a public setter method for each field (e.g., pub fn executable(&mut self, ...)), which will allow users to configure the builder’s values one by one and chain the calls together.

Further Reading

Generate Fluent Setter Methods for a Rust Builder Macro

Mục tiêu: Extend the Rust procedural macro to automatically generate the public setter method definitions for each field in the builder struct. These methods create a fluent, chainable API, and their bodies will be implemented in a subsequent task.


Of course! Let’s get this task done.


You have done an absolutely brilliant job so far. You’ve created the builder’s data structure (struct), provided a way to create it (new()), and correctly initialized its state. This is the complete foundation. Now, we move on to the most interactive part of the builder pattern: the setter methods that create the “fluent API.”

A fluent API is what allows a user to write beautiful, chainable code like Command::builder().executable("ls").args(vec!["-l"]).build(). The magic that enables this chaining is a series of methods, each responsible for setting one field and then passing the builder along for the next call. Your current task is to generate the definitions for these all-important setter methods.

Anatomy of a Fluent Setter Method

To enable method chaining, each setter method must do two things:

  1. Modify the state of the builder instance.
  2. Return the modified instance so another method can be called on it.

To achieve this, the canonical signature for a fluent setter method in Rust looks like this: pub fn field_name(&mut self, field_name: FieldType) -> &mut Self.

Let’s break down this signature piece by piece:

  • pub fn field_name: Each method is public and is named identically to the field it sets. This provides an intuitive API.
  • &mut self: This is the receiver. The method takes a mutable reference to the builder instance (self). It must be mut because the entire purpose of the setter is to change a field’s value from None to Some(...).
  • field_name: FieldType: The method takes one argument. The parameter name is also identical to the field name, and its type (FieldType) matches the type of the field in the original user struct, not the Option<T> type in our builder.
  • -> &mut Self: This is the key to fluency. The method returns a mutable reference back to itself. After a call like builder.executable("ls") completes, the expression evaluates to the builder itself, making it immediately available for the next call, like .args(...).

Generating the Setter Methods

Just as you did when generating the builder’s fields and the new() function’s initializers, we will once again iterate over our collected_fields vector. For each FieldInfo struct, we will use quote! to generate a complete public method definition according to the signature described above.

Let’s update your src/lib.rs to generate these method shells. We will place them right after the new() function inside the impl block.

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

// 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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    let field_names_for_new = collected_fields.iter().map(|field| field.name);

    // NEW: Generate the setter methods for the builder.
    // We iterate over the collected fields again.
    let setters = collected_fields.iter().map(|field| {
        let field_name = field.name;
        let field_type = field.ty;

        // For each field, generate a public method with the same name.
        // e.g., for a field `executable: String`, this generates:
        // pub fn executable(&mut self, executable: String) -> &mut Self { ... }
        quote! {
            pub fn #field_name(&mut self, #field_name: #field_type) -> &mut Self {
                // The body will be implemented in the next task. For now, it's empty.
                // We will set the field and return `self`.
            }
        }
    });

    let builder_impl = quote! {
        impl #builder_name {
            pub fn new() -> Self {
                Self {
                    #(#field_names_for_new: None),*
                }
            }

            // MODIFIED: Use the repetition syntax `#( ... )*` to splice in all the
            // generated setter methods. Note there is no comma separator, as methods
            // are not separated by commas.
            #(#setters)*
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

Code Breakdown

This is another fantastic application of Rust’s iterators combined with the power of quote!.

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

    • We create a new iterator variable named setters.
    • The .map() closure is executed for each FieldInfo in our collection.
    • Inside the closure, we extract the field_name and field_type.
    • quote! { pub fn #field_name(...) }: This is the core of the new logic. It generates a complete method signature as a TokenStream. Notice how we use #field_name twice (for the function name and parameter name) and #field_type once (for the parameter type).
    • The setters variable is now an iterator that will yield a complete method definition TokenStream for each field in the original struct.
  2. #(#setters)*:

    • Inside the main builder_impl, we place this new repetition block.
    • It iterates through our setters iterator and splices each generated TokenStream (each full method definition) into the impl block.
    • Crucially, note the separator: We use * with no preceding character (like a comma). This is because Rust syntax does not require a separator between method definitions in an impl block. quote! handles this perfectly.

You have now successfully generated the complete public API for configuring the builder! The method shells are all in place.

What’s Next?

The setter methods exist, but their bodies are empty. They don’t do anything yet! In the very next tasks, you will fill in the logic for each setter to:

  1. Assign the input value, wrapped in Some(), to the corresponding builder field.
  2. Return self to complete the fluent interface.

This will make your builder fully functional for setting values.

Further Reading

For each field, generate a public setter method: pub fn #field\_name(&mut self, #field\_name: #field\_type) -> &mut Self.

Mục tiêu:


Of course! Let’s get this task done.


You have done an absolutely brilliant job so far. You’ve created the builder’s data structure (struct), provided a way to create it (new()), and correctly initialized its state. This is the complete foundation. Now, we move on to the most interactive part of the builder pattern: the setter methods that create the “fluent API.”

A fluent API is what allows a user to write beautiful, chainable code like Command::builder().executable("ls").args(vec!["-l"]).build(). The magic that enables this chaining is a series of methods, each responsible for setting one field and then passing the builder along for the next call. Your current task is to generate the definitions for these all-important setter methods.

Anatomy of a Fluent Setter Method

To enable method chaining, each setter method must do two things:

  1. Modify the state of the builder instance.
  2. Return the modified instance so another method can be called on it.

To achieve this, the canonical signature for a fluent setter method in Rust looks like this: pub fn field_name(&mut self, field_name: FieldType) -> &mut Self.

Let’s break down this signature piece by piece:

  • pub fn field_name: Each method is public and is named identically to the field it sets. This provides an intuitive API.
  • &mut self: This is the receiver. The method takes a mutable reference to the builder instance (self). It must be mut because the entire purpose of the setter is to change a field’s value from None to Some(...).
  • field_name: FieldType: The method takes one argument. The parameter name is also identical to the field name, and its type (FieldType) matches the type of the field in the original user struct, not the Option<T> type in our builder.
  • -> &mut Self: This is the key to fluency. The method returns a mutable reference back to itself. After a call like builder.executable("ls") completes, the expression evaluates to the builder itself, making it immediately available for the next call, like .args(...).

Generating the Setter Methods

Just as you did when generating the builder’s fields and the new() function’s initializers, we will once again iterate over our collected_fields vector. For each FieldInfo struct, we will use quote! to generate a complete public method definition according to the signature described above.

Let’s update your src/lib.rs to generate these method shells. We will place them right after the new() function inside the impl block.

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

// 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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    let field_names_for_new = collected_fields.iter().map(|field| field.name);

    // NEW: Generate the setter methods for the builder.
    // We iterate over the collected fields again.
    let setters = collected_fields.iter().map(|field| {
        let field_name = field.name;
        let field_type = field.ty;

        // For each field, generate a public method with the same name.
        // e.g., for a field `executable: String`, this generates:
        // pub fn executable(&mut self, executable: String) -> &mut Self { ... }
        quote! {
            pub fn #field_name(&mut self, #field_name: #field_type) -> &mut Self {
                // The body will be implemented in the next tasks. For now, it's empty.
                // We will set the field to Some(value) and return `self`.
            }
        }
    });

    let builder_impl = quote! {
        impl #builder_name {
            pub fn new() -> Self {
                Self {
                    #(#field_names_for_new: None),*
                }
            }

            // MODIFIED: Use the repetition syntax `#( ... )*` to splice in all the
            // generated setter methods. Note there is no comma separator, as methods
            // are not separated by commas.
            #(#setters)*
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

Code Breakdown

This is another fantastic application of Rust’s iterators combined with the power of quote!.

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

    • We create a new iterator variable named setters.
    • The .map() closure is executed for each FieldInfo in our collection.
    • Inside the closure, we extract the field_name and field_type.
    • quote! { pub fn #field_name(...) }: This is the core of the new logic. It generates a complete method signature as a TokenStream. Notice how we use #field_name twice (for the function name and parameter name) and #field_type once (for the parameter type).
    • The setters variable is now an iterator that will yield a complete method definition TokenStream for each field in the original struct.
  2. #(#setters)*:

    • Inside the main builder_impl, we place this new repetition block.
    • It iterates through our setters iterator and splices each generated TokenStream (each full method definition) into the impl block.
    • Crucially, note the separator: We use * with no preceding character (like a comma). This is because Rust syntax does not require a separator between method definitions in an impl block. quote! handles this perfectly.

You have now successfully generated the complete public API for configuring the builder! The method shells are all in place.

What’s Next?

The setter methods exist, but their bodies are empty. They don’t do anything yet! In the very next tasks, you will fill in the logic for each setter to:

  1. Assign the input value, wrapped in Some(), to the corresponding builder field.
  2. Return self to complete the fluent interface.

This will make your builder fully functional for setting values.

Further Reading

Implement Setter Logic in Rust Builder Macro

Mục tiêu: Update the Rust procedural macro to generate the core logic for setter methods. This involves taking the input value, wrapping it in the Some variant of the Option enum, and assigning it to the corresponding field in the builder struct to mutate its state.


Of course! Let’s get this task done.


You’ve made phenomenal progress! In the last task, you masterfully generated the complete signatures for all the setter methods. Your macro now produces a builder with a full public API, like pub fn executable(&mut self, executable: String) -> &mut Self {}. This is the skeleton of the fluent interface. Now, it’s time to add the heart—the logic that actually sets the values.

The Setter’s Core Responsibility: Mutating State

The primary purpose of a setter method is to take an input value and update the internal state of the object. In our builder, the “state” is the collection of Option<T> fields. When a user calls a method like .executable("cargo"), our generated code must take the input "cargo" and store it in the executable field of the builder instance.

Recall how we designed the builder’s fields: each one is an Option. This was a deliberate choice. The new() function initializes every field to None, signifying an “unset” state. The setter’s job is to transition a field from the None state to the Some(value) state, signifying that the user has provided a value for that field.

Wrapping the Value with Some

The input parameter to our setter method (e.g., executable: String) has the original type T. However, the field on our builder struct (self.executable) has the type Option<T>. To make the assignment, we must wrap the incoming value in the Some variant of the Option enum. This is the crucial step that updates the builder’s state.

The line of code we need to generate for each setter’s body is simple but powerful: self.#field_name = Some(#field_name);.

Let’s break that down:

  • self.#field_name: This refers to the field on the builder struct instance. self is the mutable reference to the builder, and #field_name interpolates the name of the field we are setting.
  • Some(...): This is the constructor for the Some variant of the Option enum.
  • Some(#field_name): The #field_name inside the Some() refers to the input parameter of the function. Rust’s variable shadowing rules allow the parameter to have the same name as the struct field, which leads to this clean and idiomatic code. The value from the parameter is moved into the Some variant, which is then assigned to the struct field.

Let’s update the setters generation logic in src/lib.rs to include this single, critical line in the body of each method.

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

// 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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    let field_names_for_new = collected_fields.iter().map(|field| field.name);

    // Generate the setter methods for the builder.
    let setters = collected_fields.iter().map(|field| {
        let field_name = field.name;
        let field_type = field.ty;

        quote! {
            pub fn #field_name(&mut self, #field_name: #field_type) -> &mut Self {
                // MODIFIED: This is the new logic inside the setter.
                // It takes the input value, wraps it in `Some`, and assigns it
                // to the corresponding field on the builder struct (`self`).
                self.#field_name = Some(#field_name);
                // In the next task, we will return `self` to enable chaining.
            }
        }
    });

    let builder_impl = quote! {
        impl #builder_name {
            pub fn new() -> Self {
                Self {
                    #(#field_names_for_new: None),*
                }
            }

            // The setters are spliced in here, now with their implementation.
            #(#setters)*
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

With this change, your macro now generates fully functional setter logic. For a user’s struct Command, the executable setter will look like this:

pub fn executable(&mut self, executable: String) -> &mut Self {
    self.executable = Some(executable);
    // return self; // will be added next
}

This code correctly takes ownership of the input String, wraps it in Some, and updates the builder’s state. You’ve successfully implemented the core state-mutation logic of the builder pattern!

What’s Next?

The methods now correctly set the state, but they don’t yet enable the fluent, chainable API. If you look at the method signature ... -> &mut Self, it promises to return a mutable reference to the builder, but the method body doesn’t return anything yet. In the final task of this step, you will add the last piece of the puzzle: the return self; statement that makes method chaining possible.

Further Reading

To deepen your understanding of the concepts at play, please review these resources:

Rust Proc Macro: Implement Fluent Setters for Builder Pattern

Mục tiêu: Complete the implementation of a procedural macro for the builder pattern in Rust. This task involves modifying the generated setter methods to return &mut Self, enabling a fluent interface through method chaining.


Of course! Let’s get this task done.


You are on the verge of completing the fluent interface for your builder! In the previous task, you brilliantly implemented the core logic of the setter methods: taking the user’s input, wrapping it in Some, and assigning it to the correct field on the builder. The state mutation part is complete. The only thing left is to fulfill the promise made in the method’s signature: -> &mut Self. This final piece is the linchpin of method chaining and the very essence of a fluent API.

The Magic of the Fluent Interface: Returning self

Let’s revisit the goal of a fluent interface. We want users to be able to write code like this:

let command = Command::builder().executable("cargo").args(vec!["build"]).build();

How does this work? It’s a chain reaction. Each method call in the chain must return an object that has the next method available on it.

  1. Command::builder() returns a CommandBuilder instance.
  2. .executable("cargo") is called on that CommandBuilder instance. For the chain to continue, this call must also return a CommandBuilder instance.
  3. .args(vec!["build"]) is then called on the CommandBuilder returned by the executable call.

The signature -> &mut Self declares that each setter method will return a mutable reference to the builder instance it was called on. This is precisely what we need. The executable method modifies self and then hands that same self back, ready for the args method to be called on it.

Implicit Returns in Rust

In Rust, functions and methods return the value of the last expression in their body. Crucially, if this last expression is not followed by a semicolon (;), its value is returned to the caller. A semicolon turns an expression into a statement, which has no return value (evaluating to the unit type ()).

To return the builder instance from our setter, we simply need to make self the final expression in the method body.

The two lines of logic for each setter are:

  1. self.#field_name = Some(#field_name); // A statement that modifies state.
  2. self // An expression whose value is returned.

Let’s add this final, elegant line to your code generation logic.

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

// 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> }
    });

    let builder_struct = quote! {
        pub struct #builder_name {
            #(#builder_fields),*
        }
    };

    let field_names_for_new = collected_fields.iter().map(|field| field.name);

    // Generate the setter methods for the builder.
    let setters = collected_fields.iter().map(|field| {
        let field_name = field.name;
        let field_type = field.ty;

        quote! {
            pub fn #field_name(&mut self, #field_name: #field_type) -> &mut Self {
                self.#field_name = Some(#field_name);
                // NEW: Return a mutable reference to `self` to enable method chaining.
                // In Rust, the last expression of a function without a semicolon
                // is implicitly returned. This is the idiomatic way to return `self`.
                self
            }
        }
    });

    let builder_impl = quote! {
        impl #builder_name {
            pub fn new() -> Self {
                Self {
                    #(#field_names_for_new: None),*
                }
            }

            // The setters are spliced in here, now fully implemented.
            #(#setters)*
        }
    };

    let output = quote! {
        #builder_struct
        #builder_impl
    };

    output.into()
}

What You’ve Accomplished

Congratulations! You have now completed the entire “Implementing Builder Initialization and Field Setters” step. Your procedural macro generates a builder that is fully functional for configuration. For a user’s Command struct, you now generate complete, working setter methods like this:

pub fn executable(&mut self, executable: String) -> &mut Self {
    self.executable = Some(executable);
    self
}

pub fn args(&mut self, args: Vec<String>) -> &mut Self {
    self.args = Some(args);
    self
}

This is a massive milestone. The builder can be created, and its values can be set through a beautiful, fluent API.

What’s Next?

The builder is now a fully-configurable container for the data needed to create the final object. However, there is still no way to perform that final creation step. You have the .executable(...) and .args(...) parts, but the crucial .build() method is missing.

In the next major step, “Implementing the build Method,” you will define the final method in the builder’s impl block. This build() method will consume the data stored in the builder, perform the necessary checks, and construct and return an instance of the original struct (e.g., Command).

Further Reading

To solidify your understanding of the concepts you’ve just masterfully implemented, please review the following resources: