I claim that the builder pattern is more or less an anti-pattern and that you should use the Default trait instead. Here's why:
Let's say we have a struct:
pub struct Window {
pub title: &'static str,
pub width: usize,
pub height: usize,
}
- The builder pattern produces way too much code on the creators side while not having a significant amount of code reduction on the users side. This is especially visible if the struct has more fields:
Creator:
// Builder pattern
pub struct WindowBuilder {
__title: &'static str,
__width: usize,
__height: usize,
}
impl WindowBuilder {
pub fn new() -> Self {
Self {
__title: "Default title",
__width: 800,
__title: 600,
}
}
pub fn with_title(self, title: &'static str) -> Self {
Self {
__title: title,
__width: self.width,
__title: self.height,
}
}
pub fn with_dimensions(self, width: usize, height: usize) -> Self {
Self {
__title: self.title,
__width: width,
__title: height,
}
}
pub fn build(self) -> Window {
Window {
title: self.title,
width: self.width,
height: self.height,
}
}
}
// Default pattern: much less code!
impl Default for Window {
fn default() -> Self {
Self {
title: "Default title",
width: 800,
height: 600,
}
}
}
See how much code we need to construct a window in comparison to the Default trait?
User:
// Default pattern
let window = Window {
title: "Original title",
.. Default::default()
};
// Builder pattern: not a significant reduction of usage code!
let window = WindowBuilder::new()
.with_title("Original title")
.build();
- The builder pattern doesn't protect against double-initialization:
let window = WindowBuilder::new()
.with_title("Original title")
.with_dimensions(800, 600)
.with_title("Oops, overwritten title!")
.build();
The Default trait protects against that, because you can't initialize the same field twice. The builder pattern simply overwrites the field and you don't get any warning.
- The
Default trait eliminates the need for the SomethingBuilder struct. The SomethingBuilder struct is an intermediate struct that provides a certain kind of type safety so that you have to call SomethingBuilder.build() to construct a Something out of a SomethingBuilder. All of this is unnecessary if you use the Default trait - less code with essentially the same outcome. The SomethingBuilder has one appropriate use, in my opinion: When you need something to happen in the .build() function and it needs to happen once (although you can implement this in a default() function, too). For example, you need to tell the OS to create a window. This is where it's appropriate to use a builder. However, I've seen the builder pattern to be completely overused, which is why I'm writing this.
Often times when I'm having a struct with many fields that can have default values, it is easier to implement a default trait than to write ten or twenty builder functions. And that is why I claim that the builder pattern is actually an anti-pattern and that you should use the Default trait instead, wherever possible. It should at least be included somewhere in this repository.
I claim that the builder pattern is more or less an anti-pattern and that you should use the
Defaulttrait instead. Here's why:Let's say we have a struct:
Creator:
See how much code we need to construct a window in comparison to the
Defaulttrait?User:
The
Defaulttrait protects against that, because you can't initialize the same field twice. The builder pattern simply overwrites the field and you don't get any warning.Defaulttrait eliminates the need for theSomethingBuilderstruct. TheSomethingBuilderstruct is an intermediate struct that provides a certain kind of type safety so that you have to callSomethingBuilder.build()to construct aSomethingout of aSomethingBuilder. All of this is unnecessary if you use theDefaulttrait - less code with essentially the same outcome. TheSomethingBuilderhas one appropriate use, in my opinion: When you need something to happen in the.build()function and it needs to happen once (although you can implement this in adefault()function, too). For example, you need to tell the OS to create a window. This is where it's appropriate to use a builder. However, I've seen the builder pattern to be completely overused, which is why I'm writing this.Often times when I'm having a struct with many fields that can have default values, it is easier to implement a default trait than to write ten or twenty builder functions. And that is why I claim that the builder pattern is actually an anti-pattern and that you should use the
Defaulttrait instead, wherever possible. It should at least be included somewhere in this repository.