Crate type_state_builder

Source
Expand description

Type-State Builder Pattern Implementation

This crate provides a derive macro for implementing the Type-State Builder pattern in Rust, enabling compile-time validation of required fields and zero-cost builder abstractions.

§Overview

The Type-State Builder pattern uses Rust’s type system to enforce compile-time validation of required fields. It automatically selects between two builder patterns:

  • Type-State Builder: For structs with required fields, providing compile-time safety that prevents calling build() until all required fields are set
  • Regular Builder: For structs with only optional fields, providing a simple builder with immediate build() availability

§Key Features

  • Zero Runtime Cost: All validation happens at compile time
  • Automatic Pattern Selection: Chooses the best builder pattern for your struct
  • Comprehensive Generic Support: Handles complex generic types and lifetimes
  • Flexible Configuration: Extensive attribute-based customization
  • Excellent Error Messages: Clear guidance for fixing configuration issues

§Quick Start

Add the derive macro to your struct and mark required fields:

use type_state_builder::TypeStateBuilder;

#[derive(TypeStateBuilder)]
struct User {
    #[builder(required)]
    name: String,
     
    #[builder(required)]
    email: String,
     
    age: Option<u32>,
    active: bool, // Will use Default::default()
}

// Usage - this enforces that name and email are set
let user = User::builder()
    .name("Alice".to_string())
    .email("alice@example.com".to_string())
    .age(Some(30))
    .build();

§Supported Attributes

§Struct-level Attributes

  • #[builder(build_method = "method_name")] - Custom build method name
  • #[builder(setter_prefix = "prefix_")] - Prefix for all setter method names

§Field-level Attributes

  • #[builder(required)] - Mark field as required
  • #[builder(setter_name = "name")] - Custom setter method name
  • #[builder(setter_prefix = "prefix_")] - Custom prefix for setter method name
  • #[builder(default = "expr")] - Custom default value expression
  • #[builder(skip_setter)] - Don’t generate setter (requires default)

§Advanced Examples

§Custom Defaults and Setter Names

use type_state_builder::TypeStateBuilder;

#[derive(TypeStateBuilder)]
#[builder(build_method = "create")]
struct Document {
    #[builder(required)]
    title: String,
     
    #[builder(required, setter_name = "set_content")]
    content: String,
     
    #[builder(default = "42")]
    page_count: u32,
     
    #[builder(default = "String::from(\"draft\")", skip_setter)]
    status: String,
}

let doc = Document::builder()
    .title("My Document".to_string())
    .set_content("Document content here".to_string())
    .create(); // Custom build method name

§Generic Types and Lifetimes

use type_state_builder::TypeStateBuilder;

#[derive(TypeStateBuilder)]
struct Container<T: Clone>
where
    T: Send
{
    #[builder(required)]
    value: T,
     
    #[builder(required)]
    name: String,
     
    tags: Vec<String>,
}

let container = Container::builder()
    .value(42)
    .name("test".to_string())
    .tags(vec!["tag1".to_string()])
    .build();

§Setter Prefix Examples

use type_state_builder::TypeStateBuilder;

// Struct-level setter prefix applies to all fields
#[derive(TypeStateBuilder)]
#[builder(setter_prefix = "with_")]
struct Config {
    #[builder(required)]
    host: String,
     
    #[builder(required)]
    port: u16,
     
    timeout: Option<u32>,
}

let config = Config::builder()
    .with_host("localhost".to_string())
    .with_port(8080)
    .with_timeout(Some(30))
    .build();
use type_state_builder::TypeStateBuilder;

// Field-level setter prefix overrides struct-level prefix
#[derive(TypeStateBuilder)]
#[builder(setter_prefix = "with_")]
struct Database {
    #[builder(required)]
    connection_string: String,
     
    #[builder(required, setter_prefix = "set_")]
    credentials: String,
     
    #[builder(setter_name = "timeout_seconds")]
    timeout: Option<u32>,
}

let db = Database::builder()
    .with_connection_string("postgresql://...".to_string())
    .set_credentials("user:pass".to_string())
    .with_timeout_seconds(Some(60))
    .build();

§Optional-Only Structs (Regular Builder)

use type_state_builder::TypeStateBuilder;

// No required fields = regular builder pattern
#[derive(TypeStateBuilder)]
struct Settings {
    debug: bool,
    max_connections: Option<u32>,
     
    #[builder(default = "\"default.log\".to_string()")]
    log_file: String,
     
    #[builder(skip_setter, default = "42")]
    magic_number: i32,
}

// Can call build() immediately since no required fields
let settings = Settings::builder()
    .debug(true)
    .max_connections(Some(100))
    .build();

§Error Prevention

The macro prevents common mistakes at compile time:

use type_state_builder::TypeStateBuilder;

#[derive(TypeStateBuilder)]
struct User {
    #[builder(required)]
    name: String,
}

let user = User::builder().build(); // ERROR: required field not set
use type_state_builder::TypeStateBuilder;

#[derive(TypeStateBuilder)]
struct BadConfig {
    #[builder(required, default = "test")]  // ERROR: Invalid combination
    name: String,
}

§Module Organization

The crate is organized into several modules that handle different aspects of the builder generation process. Most users will only interact with the TypeStateBuilder derive macro.

For complete documentation, examples, and guides, see the README.

Derive Macros§

TypeStateBuilder
Derives a type-safe builder for a struct with compile-time validation of required fields.