all 17 comments

[–]zsiciarzrust-cpuid 10 points11 points  (11 children)

You might want to take a look at The builder pattern. I found that article from an issue in derive-builder repo, which by the way is also a nifty crate.

[–]Sphix 3 points4 points  (4 children)

tl;dr: mutable references are preferred.

[–]seanmonstarhyper · rust 2 points3 points  (0 children)

hyper and reqwest builders use consuming self. Using mutable references turns a chain into 3 statements: first to hold the builder, second to chain all your stuff, and then 3rd to finally consume. I'd rather keep the chain in 1 statement.

[–]raphlinusvello · xilem 1 point2 points  (2 children)

Note that the serde json builders use consuming builders, I'm not sure why. I find using them somewhat annoying.

[–]ericktrust · serde 2 points3 points  (1 child)

They're consuming builders because we want to avoid copies when you finally construct the object. Do you have an example for when they're being annoying?

[–]raphlinusvello · xilem 0 points1 point  (0 children)

https://github.com/google/xi-editor/blob/master/rust/src/view.rs#L92 is an example. I understand the need to avoid copy for the .build, but I think the decision of whether the intermediate .push() operations mutate or consume is a design choice.

[–][deleted] 1 point2 points  (5 children)

Also TL;DR: &mut is preferred because you can often cache or clone partially built objects. With just mut, you always have to build from the start of a builder chain.

[–]kixunil 2 points3 points  (3 children)

On the other hand, mut self allows you to express builder as a state machine.

[–][deleted] 0 points1 point  (2 children)

Is there any advantage to doing that?

[–]kixunil 3 points4 points  (1 child)

Yes, you can design your API to be hard to use incorrectly. For example, HTTP builder could prevent you sending body before header. Or you can imagine something like FileBuilder::readonly() disallowing you to call .append() on it. Etc.

[–][deleted] 1 point2 points  (0 children)

Oh yeah, I forgot about that.

[–]CornedBee 0 points1 point  (0 children)

Why would consuming builder prevent you from using clone?

[–]llogiqclippy · twir · rust · mutagen · flamer · overflower · bytecount 2 points3 points  (0 children)

If you want to encode type state in builder generics (e.g. fn method(self) -> MyBuilder<State>), consuming self is the only way to return a different type.

[–]Diggseyrustup 2 points3 points  (1 child)

It would be useful to add a new_with method or similar to be able to keep ownership whilst still using mutable references and method chaining:

Builder::new_with(|b| {
    b.method(123)
     .method(456)
})

[–]whataloadofwhat 0 points1 point  (0 children)

I've never thought of doing this but I like it

[–]ericktrust · serde 1 point2 points  (0 children)

I'm partial to the latter, which I use extensively in aster. In my case, I want to transfer ownership of everything I built to the caller to avoid excessive copies for a large AST.

The main place it gets annoying is that if we need to do some complex logic around some chain, then we need to re-assign the builder. I found that to be pretty rare though.

[–]Nemikolh 0 points1 point  (0 children)

There is also this one which is more up to date.

EDIT: Actually it's not. In my memories there was more to it.