Hello folks. So I’m still not good at Rust and learn even basics after years (just on and off doing some stuff). I’m currently working on my first small GUI application with FLTK in Rust. It’s not that important for my question, but I think this gives a bit of context. The actual question is about struct and impl, using a builder pattern like pattern, but without impl builder and build() function.

Normally with the builder pattern, there are at least two structs and impl blocks. One dedicated to build the first struct. But I am doing it with only one struct and impl block, without a build() function. But it is functionally (at least conceptional) the same, isn’t it? A shorted example for illustration:

Edit: Man beehaw is ruining my code blocks removing the opening character for >, which wil be translated to < or or completely removed. I use a % to represent the opening.

struct AppSettings {
    input_directory: Option%PathBuf>,
    max_depth: u8,
}

impl AppSettings {
    fn new() -> Self {
        Self {
            input_directory: None,
            max_depth: 1,
        }
    }

    fn input_directory(mut self, path: String) -> Self {
        self.input_directory = match path.fullpath() {
            Ok(p) => Some(p),
            Err(_) => None,
        };

        self
    }

    fn max_depth(mut self, levels: u8) -> Self {
        self.max_depth = levels;

        self
    }
}

And this is then used in main like

    let mut appsettings = AppSettings::new()
        .input_directory("~/test".to_string())
        .max_depth(3);

BTW I have extended PathBuf and String with a few traits. So if you wonder why I have code like this path.fullpath() . So just ignore that part. I’m just asking about the builder pattern stuff. This works for me. Do I miss something? Why would I go and do the extra step of creating another struct and impl block to build it and a final struct, that is basically the same? I don’t get that.

Is this approach okay in your mind?

  • nous@programming.dev
    link
    fedilink
    English
    arrow-up
    5
    ·
    2 months ago

    The basic idea behind the builder pattern is to ensure the main thing can only ever exist in a valid state, no half valid values letting you call things in weird ways that break.

    Here AppSettings is not the thing you care about, the App is. So you can think of AppSettings as a builder for the App. A final call to it should construct a valid App and you should not be able to do that when the settings are invalid.

    If there are one or two required fields the having those on the new method of the AppSettings or the final build method that constructs the app is a good approach. If there is a set order things need to be created in then the generic state pattern or multiple structs can be used instead to limit what functions are available at each stage.

    • thingsiplay@beehaw.orgOP
      link
      fedilink
      arrow-up
      1
      ·
      2 months ago

      I now understand the benefit of the two step approach. I’ll dive into this topic today and decide if its worth for my use case.

      There is already a valid app (from FLTK gui) with hardcoded values so far, to get something running. Now I’m creating configuration for the program, that will include from config file, commandline options and defaults. The app is basically the gui and created as first and independent from appsettings. So in my case i would have an AppSettingsBuilder, because that is the part I am in full control over its creation.

      • nous@programming.dev
        link
        fedilink
        English
        arrow-up
        1
        ·
        2 months ago

        I don’t see why you need a separate builder when you are trying to build the app, even if the app is a third part thing you can still build it as part of the final method. Assuming there are no other options needed.