Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Constructors and Builders

Rule

Use new or try_new for required fields, add a builder for substantial optional configuration, and use typestate builders only for important required steps.

Why

Constructors keep invariants close to a type. Builders improve named optional configuration but add API surface and should earn that cost.

Do

  • Use new for infallible construction from required values.
  • Use try_new when construction validates or can fail; reserve parse for FromStr-backed text parsing.
  • Follow ownership policy for constructor parameters.
  • Keep validation inside the constructor or build method.
  • Implement Default only for an obvious, useful value.
  • Use a builder when defaults or optional fields would otherwise produce boolean flags and repeated None arguments.
  • Prefer consuming setters for owned configuration builders.
  • Use typestate only when a missing or out-of-order step would violate an important invariant.

Avoid

  • Do not add a builder for every struct.
  • Do not let new panic on caller-provided input.
  • Do not use long positional constructors with boolean flags.
  • Do not encode ordinary optional settings with typestate.
  • Do not provide a surprising, invalid, or environment-dependent Default.

Public API Notes

Adding a required constructor parameter is normally breaking. Optional builder methods are easier to add compatibly to published APIs.

Example

#![allow(unused)]
fn main() {
#[derive(Clone, Debug)]
pub struct ClientOptions {
    endpoint: String,
    timeout: Duration,
}

impl ClientOptions {
    pub fn new(endpoint: impl Into<String>) -> Self {
        Self {
            endpoint: endpoint.into(),
            timeout: Duration::from_secs(30),
        }
    }

    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }
}
}

Exceptions

  • Use struct literals for intentional plain data.
  • Use &mut self setters when callers must reuse a builder or an existing API establishes that style.
  • Use a builder crate only when builder-heavy code justifies the dependency.