Starting a new unofficial side project trying to bring some rust to work 🙃

Hello all,

First, I want to wish everyone well during these trying times. I also am hoping to bring some good news. I am trying to put a little Rust in my work mix :wink:

This crate is/will be used to generate urls specific to a service we provide at work. Day to day I'm tackling stuff like this in java, ruby, python, +. However, after being influenced by ripgrep, cargo, et al. I find builder-patterned interfaces to be much more configurable than say a telescoping constructor pattern in java.

I was hoping to get some feedback here. Are builder interfaces really as convenient as I feel they are for configuring interfaces? What other patterns would you suggest?

1 Like

I only have one qualm with the builder pattern, I really like to const things if I can.

The instances of the builder pattern that I have seen, require the builder to be mut, even when the thing it is actually building could be. In these cases if you can't opt out of it, it can get in the way.

Not that I have really found a good replacement, and I'm not sure how many/much of your urls it might be possible use with const.

I unfortunately haven't been able to find a pattern that is as convenient and works with const :sob:.

good luck with your project :sailboat:

2 Likes

Thanks! Yeah, you're right. You're also right about there not really being a replacement. In one text on the subject that's one of the consequences of using the pattern.

I'm still trying to

find a pattern that is as convenient and works with const

But where I've settled for now is trying to enforce strong semantics about what it means for the structure to be in a valid state at each step of the process. Maybe there's a solution where this pattern gets exposed along with a pattern that allows for a more const or immutable friendly option.

Maybe I could write Builder where the building functions take self and return a newly constructed Builder { --snip-- } then write a BuilderMut that takes mut self during construction and mutates the current struct and returns the mutated form... Hmmm. :thinking:

Another option is to use the functional update syntax which lets you construct a new object by copying the fields from an existing one.

For example,

struct Foo {
  first: u32,
  second: String,
  third: Vec<u8>,
}

impl Default for Foo {
  fn default() -> Foo {
    Foo { 
      first: 42,
      second: String::from("Hello, World!"),
      third: Vec::new(),
    }
  }
}

fn main() {
  let my_custom_thing = Foo {
    second: String::from("Some other string"),
    ..Default::default()
  };
}

So you could implement Default for your type and people override the fields they care about, then use ..Default::default() to fill in the rest using Foo's Default implementation.

The only real limitation is that all fields must be publicly accessible and downstream user can't use it if you've annotated the struct with the #[non_exhaustive] attribute (according to the #[non_exhaustive] RFC).

3 Likes

This is really great information, thank you! And yeah, that's what I was getting with

functions take self and return a newly constructed Builder { --snip-- }

E.g. fn field(self, f: String) -> Self { ... } , but alas functional update syntax is the right set of words to describe the goal here (especially since self could be omitted entirely). Thank You!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.