Builder Pattern
Recipe | Crates | Categories |
---|---|---|
Construct a Complex Struct with a Builder | ||
bon | ||
derive_builder | ||
typed-builder |
FIXME
Construct a Complex Struct with a Builder
The builder pattern provides a clean and readable way to construct complex objects, especially when dealing with multiple optional fields or validation rules.
- Instead of creating the object directly, you first create a separate Builder object. It is typically named
xyzBuilder
wherexyz
is the name of the struct you're building. The builder holds the state needed to construct the final object, often storing fields asOption<T>
internally. - The builder provides methods (often named after the fields they set, like
.name("Example")
,.timeout(Duration::from_secs(5))
) to configure the object piece by piece. These methods usually returnself
, allowing you to chain calls together fluently:builder.field_a(value_a).field_b(value_b)
. - Once the desired fields are set, you call a final method on the builder (commonly named
.build()
or.finish()
). This method takes the configuration stored in the builder, performs any necessary validation (like checking if required fields were provided), and then constructs and returns the final, fully-formed object. If the validation can fail, thebuild
method typically returns aResult
.
#![allow(dead_code)] //! Example builder pattern. /// The object to build. #[derive(Debug)] struct DatabaseConfig { host: String, port: u16, username: Option<String>, password: Option<String>, pool_size: u32, connection_timeout_seconds: u64, // More fields... } impl DatabaseConfig { /// Optional: you may provide a method that returns a builder. fn build(host: String, port: u16) -> DatabaseConfigBuilder { DatabaseConfigBuilder::new(host, port) } // Note that there are no constructor-like method otherwise. } /// The builder object. struct DatabaseConfigBuilder { host: String, port: u16, username: Option<String>, password: Option<String>, pool_size: u32, connection_timeout_seconds: u64, } impl DatabaseConfigBuilder { /// The builder's constructor (with required fields, if necessary). fn new(host: String, port: u16) -> DatabaseConfigBuilder { DatabaseConfigBuilder { host, port, // The optional fields are set to None or default values. username: None, password: None, pool_size: 10, connection_timeout_seconds: 30, } } /// Note that the builder's methods consume `self` and returns `Self`. fn username(mut self, username: String) -> Self { self.username = Some(username); self } fn password(mut self, password: String) -> Self { self.password = Some(password); self } fn pool_size(mut self, pool_size: u32) -> Self { self.pool_size = pool_size; self } fn connection_timeout_seconds( mut self, connection_timeout_seconds: u64, ) -> Self { self.connection_timeout_seconds = connection_timeout_seconds; self } fn build(self) -> DatabaseConfig { DatabaseConfig { host: self.host, port: self.port, username: self.username, password: self.password, pool_size: self.pool_size, connection_timeout_seconds: self.connection_timeout_seconds, } } } fn main() { // Create a builder struct. let config = DatabaseConfigBuilder::new("localhost".to_string(), 5432) // Call (some of) the builder's methods to initialize or update the fields. .username("admin".to_string()) .password("secret".to_string()) .pool_size(20) .build(); println!("Database Configuration: {:?}", config); }
typed-builder
typed-builder
⮳ lets you derive compile-time type-checked builders. It uses a derive macro and leverages Rust's type system to ensure that all required fields are set. If you forget a required field, your code won't even compile.
derive_builder
derive_builder
⮳ provides a #[derive(Builder)]
macro to automatically implement the builder pattern for arbitrary structs. It performs checks at runtime within the .build()
method.
bon
bon
⮳ is a compile-time-checked builder generator with additional features like support for fallible/async builders and named function arguments via the builder pattern.