ideasman42
ideasman42

Reputation: 48048

How to write an idiomatic build pattern with chained method calls in Rust?

Based on the following examples, its possible to write a build-pattern with chained method calls in Rust which either passes by value or by reference (with a lifetime specifier)

A builder pattern in Rust may look something like this:

 ui::Button::new()
    .label("Test")
    .align(Align::Center)
    .build();

When writing idiomatic Rust is there a strong preference for one over another?

Is there some good example of how to write this in Rust?

Upvotes: 4

Views: 5806

Answers (2)

Matthieu M.
Matthieu M.

Reputation: 299810

There are actually two trade-offs:

  • should the named setter accept self by value or reference?
  • should the final build method accept self by value or reference?

My recommendation is:

  • mutable reference for the setters
  • value for the build method

This differs slightly from the Builder Pattern presented in the Rust Book which uses a reference in build.


Why passing by mutable reference for the setters?

While a compiler may optimize away the moves caused by a call to fn label(self, &str) -> ButtonBuilder, it is not guaranteed.

On the other hand, the mutable reference way is already optimal so that you need not rely on the optimizer.


Why passing by value for the final build?

For builders only composed of Copy fields, there is no difference between build taking self or &self.

However, as soon as the builder contains non-Copy fields, passing &self to build requires deep-cloning these fields.

On the other hand, passing self by value allows build to move the fields, which avoid unnecessary copies.

If one wishes to re-use the builder, then the builder should implement Clone.

Upvotes: 8

Nathan Ringo
Nathan Ringo

Reputation: 1003

I've seen the builder pattern mostly implemented by taking ownership of the Builder when modifying it, and by reference for build(). For example,

#[derive(Debug, Eq, PartialEq)]
struct Foo {
    value: usize,
}

struct FooBuilder {
    foos: usize,
    bars: usize,
}

impl FooBuilder {
    fn new() -> FooBuilder {
        FooBuilder {
            foos: 0,
            bars: 0,
        }
    }
    fn set_foos(mut self, foos: usize) -> FooBuilder {
        self.foos = foos;
        self
    }
    fn set_bars(mut self, bars: usize) -> FooBuilder {
        self.bars = bars;
        self
    }
    fn build(&self) -> Foo {
        Foo {
            value: self.foos + self.bars,
        }
    }
}

fn main() {
    let foo = FooBuilder::new()
        .set_foos(2)
        .set_bars(3)
        .build();
    assert_eq!(foo, Foo { value: 5 });
}

Try on Rust Playground

This makes chaining simple, while allowing reuse of the builder.

Upvotes: 4

Related Questions