Live data from Hacker News

Builder Pattern in Rust

greyblake.com

1–10 of 93 posts

Re: Builder Pattern in Rust

#2
I appreciate the tradeoffs that led to the builder pattern becoming commonplace, but it's probably my least favorite part of Rust. Compared to optional named parameters it just feels clunky, verbose and kind of Java-esque.

Re: Builder Pattern in Rust

#4
post #2

I appreciate the tradeoffs that led to the builder pattern becoming commonplace, but it's probably my least favorite part of Rust. Compared to optional named parameters it just feels clunky, verbose and kind of Java-esque.

I agree. I’ve seen a few libraries (I can’t remember which) instead take the approach of a constructor that takes a single-use struct as an argument. The struct implements Default and all of its members are public. You can then use ..Struct::default() to splice in the default values, and override only the values you want to set.

It ends up being an approximation of named optional arguments, albeit with a somewhat more clunky syntax.

Re: Builder Pattern in Rust

#5
post #2

I appreciate the tradeoffs that led to the builder pattern becoming commonplace, but it's probably my least favorite part of Rust. Compared to optional named parameters it just feels clunky, verbose and kind of Java-esque.

I hope that one day the default struct field values RFC will be reconsidered: https://github.com/rust-lang/rfcs/pull/1806.

This would allow a syntax more familiar to users of JavaScript, where fields with their default value can be omitted:

  struct Person {
    name: String,
    email: Option = None,
  }

  let person = Person {
    name: "John Doe".to_owned(),
    ..
  };

Re: Builder Pattern in Rust

#6
Where I've really seen builders be useful is when you have lots and lots of parameters, where most of the time defaults are fine, and you may actually want to pass then around to other parts of the systems, perhaps with different dependencies, who will then further modify them.

An example of this would be configuring a client. Your unit tests may want the client to literally just be a function that returns "OK", your integration tests may want the client dynamically take different parameters depending on what part of the system is being tested (but leave the rest the same), and in production it may need to go through some kind of proxy that your company has.

If you just have one method to construct these things, in each of those many, many different places, you have these huge long constructors being called. It becomes difficult to audit, so mistakes can be made as the requirements evolve, but also, even worse, some chap always comes along and refactors the whole system to optimize for "code reuse", but instead makes a single chokepoint where now one change in one part of the system necessitates breaking every other part of the system. The problem is you don't actually experience that until later down the road, but by that point said chap from before has already been touted as a thought leader in the organization, etc...

Builders are really good in these cases. Especially since you can pass the builder objects along and modify them along the way, and then inject them as dependencies where needed. You can also add special typing if you need to provide certain guard rails, I.e. 'AuthorizedClient' once you've supplied username + password, or whatever.

Re: Builder Pattern in Rust

#7
post #2

I appreciate the tradeoffs that led to the builder pattern becoming commonplace, but it's probably my least favorite part of Rust. Compared to optional named parameters it just feels clunky, verbose and kind of Java-esque.

IMO, the toy example in the article is too simple to show a good use of the pattern. The builder pattern is great if the builder methods configure complicated invariants and are not just setters. Otherwise, there's nothing wrong with making the struct public, as in

  User { id, email, ..Default::default() }
What's Java-esque is having an irrational fear of using public fields for things that are just data containers, but that's in my experience rare in the Rust community.

Re: Builder Pattern in Rust

#8
Interesting, i wouldn't reach for a builder pattern in the article's scenario:

    struct User {
      email: Option,
      first_name: Option,
      last_name: Option
    }
My gut feel would have been to drop the Option and just have different structs with different combinations of fields as needed. E.g. You could consume an PersonName type with an EmailAddress type to produce a User type if that's what you needed.

Re: Builder Pattern in Rust

#9
post #5
post #2

I appreciate the tradeoffs that led to the builder pattern becoming commonplace, but it's probably my least favorite part of Rust. Compared to optional named parameters it just feels clunky, verbose and kind of Java-esque.

I hope that one day the default struct field values RFC will be reconsidered: https://github.com/rust-lang/rfcs/pull/1806 . This would allow a syntax more familiar to users of JavaScript, where fields with their default value can be omitted: struct Person { name: String, email: Option = None, } let person = Person { name: "John Doe".to_owned(), .. };

This is probably obvious and it isn't 100% alternative to Javascript syntax, but you can leverage Default trait to come a bit closer:

  #[derive(Default)]
  struct Person {
      name: String,
      email: Option,
  }

  fn main() {
      let person = Person {
          name: "John Doe".to_owned(),
          ..Default::default()
      };
  }
Post reply on HN