Rust's builder pattern, as described in the book "Effective Rust"
⚓ Rust 📅 2026-01-05 👤 surdeus 👁️ 5Over Christmas, I read a few pages of David Drysdale's book "Effective Rust", which is also available online at Effective Rust - Effective Rust.
I think that book is actually a quite good second Rust book -- from the text quality and issue rate perhaps one of the best Rust book.
The author explains some of the Rust concepts in his own words, which is quite useful as a repetition and to re-foster the understanding.
In Item #7 he explains Rust's builder pattern, which can be useful to initialize structs with fields without a valid default value, like time and date values. He introduces the builder scheme, with a helper Build struct and an explicit build() function. I think I have read about that strategy at other places as well. So he uses
pub struct DetailsBuilder(Details);
impl DetailsBuilder {
/// Start building a new [`Details`] object.
pub fn new(
given_name: &str,
family_name: &str,
date_of_birth: time::Date,
) -> Self {
DetailsBuilder(Details {
given_name: given_name.to_owned(),
preferred_name: None,
middle_name: None,
family_name: family_name.to_owned(),
mobile_phone: None,
date_of_birth,
last_seen: None,
})
}
}
and
/// Consume the builder object and return a fully built [`Details`]
/// object.
pub fn build(self) -> Details {
self.0
}
to allow a variable construction like
let also_bob = DetailsBuilder::new(
"Robert",
"Builder",
time::Date::from_calendar_date(1998, time::Month::November, 28)
.unwrap(),
)
.middle_name("the")
.preferred_name("Bob")
.just_seen()
.build();
See Item 7: Use builders for complex types - Effective Rust
I wonder why (for this use case) the additional Builder struct and calling the build() function is actually required. The same functionality seems to be possible with just a constructor function requiring the parameters that needs to be explicitly specified. So calling a final build() is not required. For me this looks like a more natural instantiation strategy also known from other programming languages:
/// Phone number in E164 format.
#[derive(Debug, Clone)]
pub struct PhoneNumberE164(pub String);
#[derive(Debug)]
pub struct Details {
pub given_name: String,
pub preferred_name: Option<String>,
pub middle_name: Option<String>,
pub family_name: String,
pub mobile_phone: Option<PhoneNumberE164>,
pub date_of_birth: time::Date,
pub last_seen: Option<time::OffsetDateTime>,
}
impl Details {
/// Start building a new [`Details`] object.
pub fn new(given_name: &str, family_name: &str, date_of_birth: time::Date) -> Self {
Details {
given_name: given_name.to_owned(),
preferred_name: None,
middle_name: None,
family_name: family_name.to_owned(),
mobile_phone: None,
date_of_birth,
last_seen: None,
}
}
pub fn preferred_name(mut self, preferred_name: &str) -> Self {
self.preferred_name = Some(preferred_name.to_owned());
self
}
pub fn middle_name(mut self, middle_name: &str) -> Self {
self.middle_name = Some(middle_name.to_owned());
self
}
pub fn just_seen(mut self) -> Self {
self.last_seen = Some(time::OffsetDateTime::now_utc());
self
}
}
fn main() {
let bob = Details::new(
"Robert",
"Builder",
time::Date::from_calendar_date(1998, time::Month::November, 28).unwrap(),
)
.middle_name("the")
.preferred_name("Bob")
.just_seen();
println!("OK, {:?}", bob.preferred_name);
}
See playground link: Rust Playground
This code seems to compile and work. So what is the actual advantage of the explicit builder pattern used in the book? Or is it just that the book was written in 2024 but still for the Rust 2018 edition?
2 posts - 2 participants
🏷️ Rust_feed