Builder pattern implementation

I am trying to implement a builder for a Record type, which as you might guess, represents one of the tuples in a database relation:

#[derive(Eq, Hash, PartialEq, Default)]
pub struct Record<const N: usize>
{
    pub utility: SVector<i64, N>,
    pub draw: u64,
    pub rem: u64,
    pub mex: u64,
}

Now, I have seen online that canonically, a builder pattern is implemented by creating a separate RecordBuilder type.

My question is, what is stopping me from doing something like the following:

impl<const N: usize> Record<N>
{
    fn with_utility(mut self, utility: SVector<i64, N>) -> Self
    {
        /// Stuff, potentially
        self.utility = utility;
        self
    }

    ...
}

...to avoid the conceptual/cleanliness overhead of having a RecordBuilder with a build method?

As a follow up: I am unexperienced with tools like CompilerExplorer -- does calling builder methods in the following way:

let result = BuildableType::default()
    .with_field1("data")
    ...
    .with_fieldN("data")

...mean that rustc can inline the sequential calls into something (somewhat) equivalent to the following:

let result = {
    BuildableType {
        field1: { 
            /// Stuff
            "data"
        },
        ...
        fieldN: { 
            /// Stuff
            "data"
        },
    }
}

...to avoid unnecessary memory jumps?

Thanks!

Nothing is stopping you from implementing it as you said. The builder pattern with the build method can be leveraged, for example, to validate that the resulting struct can be built; another use case is that every method from the builder pattern returns a special intermediate type, which opens new possibilities for further customization/validation that are simply not possible with the simple "return Self" version.

2 Likes

I love and write builders like that A LOT.
Actually, you and I are not alone, someone gave it a name: Builder Lite
As said by @moy2010, having a separate type is nice when you need additional layer of validation, but you don’t always need all this flexibility (especially for internal APIs where you don’t even have to worry about whether you may need this flexibility in the future).

4 Likes