The Builder Pattern
Construct objects step by step.
The Builder Pattern is a free Learn Rust Coding lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why a Builder?
Rust has no named or optional function arguments. When a struct has many fields, especially optional ones, a constructor with eight positional parameters becomes unreadable and error-prone.
The builder pattern solves this. You configure an object step by step with named methods, then call a final build() to produce the value. It reads like a fluent sentence.
The Target Struct
Start with the type you actually want to construct. Here a server configuration carries a required host plus several optional knobs.
Notice the fields are private to encourage construction through the builder rather than struct literals.
pub struct ServerConfig {
host: String,
port: u16,
max_connections: usize,
use_tls: bool,
}A Separate Builder Type
The classic approach uses a second struct, the builder. It mirrors the target but stores work-in-progress state. Optional fields often become Option<T> so you can tell "unset" from "explicitly set".
pub struct ServerConfigBuilder {
host: String,
port: Option<u16>,
max_connections: Option<usize>,
use_tls: bool,
}Starting the Builder
Give the builder a constructor that takes only the required fields. Everything optional starts as None or a default.
A common convention is a builder() method on the target type that returns the builder.
impl ServerConfig {
pub fn builder(host: impl Into<String>) -> ServerConfigBuilder {
ServerConfigBuilder {
host: host.into(),
port: None,
max_connections: None,
use_tls: false,
}
}
}Setter Methods Take self by Value
Each setter consumes self, mutates a field, and returns self. Returning the owned value lets you chain calls fluently.
This ownership-based chaining is the idiomatic Rust style and avoids lifetime headaches.
impl ServerConfigBuilder {
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn use_tls(mut self, yes: bool) -> Self {
self.use_tls = yes;
self
}
}Applying Defaults in build()
The terminal build() turns the builder into the real type. This is where you fill in defaults for anything still None using unwrap_or.
impl ServerConfigBuilder {
pub fn build(self) -> ServerConfig {
ServerConfig {
host: self.host,
port: self.port.unwrap_or(8080),
max_connections: self.max_connections.unwrap_or(128),
use_tls: self.use_tls,
}
}
}Fluent Construction
Now construction reads top to bottom. Required data goes into builder(); each optional tweak is a clearly named call.
Fields you skip silently take their defaults.
fn main() {
let cfg = ServerConfig::builder("localhost")
.port(9000)
.use_tls(true)
.build();
println!("{}:{} tls={}", cfg.host, cfg.port, cfg.use_tls);
}A Complete Runnable Example
Here is the whole pattern condensed into one program you can run. It shows that skipped fields fall back to defaults inside build().
struct Config { name: String, retries: u32 }
struct Builder { name: String, retries: Option<u32> }
impl Config {
fn builder(name: &str) -> Builder {
Builder { name: name.to_string(), retries: None }
}
}
impl Builder {
fn retries(mut self, n: u32) -> Self { self.retries = Some(n); self }
fn build(self) -> Config {
Config { name: self.name, retries: self.retries.unwrap_or(3) }
}
}
fn main() {
let c = Config::builder("job").build();
println!("{} retries={}", c.name, c.retries);
}Fallible build with Result
Sometimes a configuration can be invalid, for example a port of zero. Make build() return Result so validation failures surface as recoverable errors instead of panics.
impl ServerConfigBuilder {
pub fn try_build(self) -> Result<ServerConfig, String> {
let port = self.port.unwrap_or(8080);
if port == 0 {
return Err("port must be non-zero".into());
}
Ok(ServerConfig { host: self.host, port,
max_connections: self.max_connections.unwrap_or(128),
use_tls: self.use_tls })
}
}The derive_builder Crate
Writing builders by hand is repetitive. The derive_builder crate generates the whole builder from an annotation.
You annotate fields with defaults and get a generated FooBuilder with setters and a fallible build() for free.
use derive_builder::Builder;
#[derive(Builder)]
struct Channel {
#[builder(default = "8080")]
port: u16,
name: String,
}Owned vs Mutable-Reference Builders
Two styles exist. The owned style consumes self and chains naturally. The &mut self style returns &mut Self and lets you split building across statements without re-binding.
Owned is more common for one-shot construction; the mutable style suits conditional configuration in loops.
impl ServerConfigBuilder {
pub fn port_ref(&mut self, port: u16) -> &mut Self {
self.port = Some(port);
self
}
}Quick Check
Test your understanding of the owned-self builder style.
Recap
The builder pattern works around Rust's lack of optional arguments. A builder type holds work-in-progress state, setters consume and return self for chaining, and build() applies defaults to produce the final value.
Use Result from build() for validation, and reach for derive_builder to skip the boilerplate.
Frequently asked questions
Is the “The Builder Pattern” lesson free?
Yes — the full text of “The Builder Pattern” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “The Builder Pattern”?
Construct objects step by step. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “The Builder Pattern” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Builder Pattern
- The Newtype Pattern
- Type-State Builders
- Deref and Wrapper Ergonomics