Builder pattern in Rust

Page content

https://rust-unofficial.github.io/patterns/patterns/creational/builder.html

TL;DR

Construct an object with calls to a builder helper.

Explain with Rust example: step by step

I guess this Pasta example is a bad example… need to be improved

Suppose you define a new struct (I use &str because it is just a snippet, but in real code, you should use String in this case):

struct Pasta<'a> {
    name: &'a str,
    tomato: bool,
    garlic: bool,
    special_order: &'a str
}

You can create/initialize an object like below:

let pasta_obj = Pasta {
                  name: "Margherita",
                  tomato: true,
                  garlic: false,
                  special_order: "less-boiled"
              }

But if Pasta has a lot of fields, the line would be long. Moreover, if you want to calculate values before fields are set, you need to create a function which takes Pasta object as a parameter. (Otherwise, code would be long and unreadable.)

Think about the following code:

let pasta_obj = PastaBuilder::new()
                .template("Margherita") // it will set all required fields.

Once you write template function, this code is readable, and the responsibility of creation is moved to that function. Moreover, this code would be more convenient (fluent interface):

let pasta_obj = PastaBuilder::new()
                .template("Margherita")
                .cheese("Emmental")

To achieve this convenience, one can introduce pasta-builder.

struct PastaBuilder<'a> {
    boul: Vec<&'a str>,
    knife: bool,
}
impl<'a> PastaBuilder<'a> {
    fn new() -> Self {
        Self {
            boul: vec![],
            knife: true
        }
    }

    fn add(self, vegetable: &'a str) -> Self {
        self.boul.push(vegetable);
        self
    }

    fn build(self) -> Pasta<'a> {
        // cooking with boul and knife
        // complex cooking method
        Pasta {
            // builded pasta object
        }
    }
}

You can regard this PastaBuilder as a helper to create Pasta instance.

Advantages

https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#advantages

  • Separates methods for building from other methods.
  • Prevents proliferation of constructors
  • Can be used for one-liner initialisation as well as more complex construction.

Example in std

As another example, std::process::Command is a builder for std::process::Child, and you can append arguments as many times you want:

use std::process::Command;

let output = if cfg!(target_os = "windows") {
    Command::new("cmd")
            .args(["/C", "echo hello"])
            .output()
            .expect("failed to execute process")
} else {
    Command::new("sh")
            .arg("-c")
            .arg("echo hello")
            .output()
            .expect("failed to execute process")
};

let hello = output.stdout;

When you don’t need builder

If the constructor is simple, you can simply implement constructor for the struct.

https://rust-unofficial.github.io/patterns/idioms/ctor.html

Default trait (constructor) is useful in this case.

Builder pattern is seen more frequently in Rust

https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#discussion

Rust lacks overloading (by design). Since you can only have a single method with a given name, having multiple constructors is less nice in Rust than in C++, Java, or others.

cf) Why does Rust have no overload?

https://users.rust-lang.org/t/is-there-a-simple-way-to-overload-functions/30937/3

Because function overloading is conceptually a missconception. Logically if we assume that function name is describing a behaviour, there is no reason, that the same behaviour may be performed on two completelly independent, not simillar types - which is what overloading actually is. If there is a situation, where operation may be performed on set of types, its clear, that they have something in common, so this commoness shouold be extracted to trait, and then generalizing function over trait is straight way to go.

Typestate Programming

Once you understand the builder pattern, you can also understand typestate programming in Rust.

https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html