typed-builder

repository·master·Indexed 22 days ago

https://github.com/idanarye/rust-typed-builder

A Rust library providing a custom derive macro to generate compile-time verified builders. It ensures all mandatory fields are provided and no field is set more than once during construction. Supports field customization via #[builder(...)] attributes for defaults, setter transformations (into, strip_option, strip_bool), and custom mutators for modifying builder state.

Tokens
2.4K
Snippets
6
Records
9
Agent score
77%

What's inside typed-builder

  1. Understand TypedBuilder limitations and behavior

    master

    When using typed-builder, keep the following architectural constraints in mind:

    • Error Reporting: If you forget a field or set a field twice, the compiler error may appear as a deprecation warning rather than a primary error.
    • Builder Lifecycle: All builder methods are call-by-move. The builder is not Clone and is not intended to be passed around or stored; it is designed strictly for ergonomic object creation.
    • Complexity: The generated builder type uses many generic parameters and has an internal name that is not meant for manual manipulation.
    • Performance: Unlike safe-builder-derive which can cause exponential build times, typed-builder encodes state in generic arguments, ensuring Rust only generates the code paths you actually use.
  2. Use the TypedBuilder derive macro

    master

    To create a compile-time verified builder, derive TypedBuilder on your struct. This allows you to construct the struct using a builder pattern where the compiler ensures all mandatory fields are set exactly once before calling .build().

    use typed_builder::TypedBuilder;
    
    #[derive(TypedBuilder)]
    struct Foo {
        x: i32,
        #[builder(default, setter(strip_option))]
        y: Option<i32>,
        #[builder(default=20)]
        z: i32,
    }
    
    // Usage:
    Foo::builder().x(1).y(2).z(3).build();
    Foo::builder().z(1).x(2).y(3).build();
    
    // Omit fields with #[builder(default)]:
    Foo::builder().x(1).build();
  3. How Mutators work in TypedBuilder

    master

    Mutators allow you to define custom logic that modifies the builder's state. You define them at the struct level using #[builder(mutators(...))].

    Fields can be made available to mutators in two ways:

    1. via_mutators: Fields marked with #[builder(via_mutators)] are automatically accessible to all mutators. You can also provide an initial value using #[builder(via_mutators(init = ...))].
    2. requires: If a mutator needs to access a field that is NOT marked via_mutators, you must explicitly declare it using #[mutator(requires = [field_name])] on the mutator function.

    Note: Mutators do not enforce being called only once; they can be called multiple times to accumulate changes (e.g., pushing to a Vec).

    use typed_builder::TypedBuilder;
    
    #[derive(PartialEq, Debug, TypedBuilder)]
    #[builder(mutators(
        fn inc_a(&mut self, a: i32){
            self.a += a;
        }
        #[mutator(requires = [x])]
        fn x_into_b(&mut self) {
            self.b.push(self.x)
        }
    ))]
    struct Struct {
        #[builder(mutators(
            fn x_into_b_field(self) {
                self.b.push(self.x)
            }
        ))]
        x: i32,
        #[builder(via_mutators(init = 1))]
        a: i32,
        #[builder(via_mutators)]
        b: Vec<i32>
    }
    
    assert_eq!(
        Struct::builder().x(2).x_into_b().x_into_b().x_into_b_field().inc_a(2).build(),
        Struct {x: 2, a: 3, b: vec![2, 2, 2]}
    );
  4. Configure the builder using type-level attributes

    master

    You can customize the generated builder by applying #[builder(...)] to the struct itself.

    Available type-level options:

    • doc: Enables documentation for the builder type and its build method (defaults to #[doc(hidden)]).
    • crate_module_path: Used when typed_builder is re-exported. Defaults to ::typed_builder.
    • builder_method(...): Customize the method that creates the builder.
    • builder_type(...): Customize the name/type of the builder. Supports attributes(...) to add derives (e.g., #[derive(Debug)]) to the generated builder type.
    • build_method(...): Customize the final .build() method. Supports:
      • vis: Visibility (default pub).
      • name: Method name (default build).
      • doc: Custom documentation string.
      • into: Change the output type via Into conversion.
    • field_defaults(...): Sets default options for all fields in the struct.
    • mutators(...): Defines functions that can mutate fields within the builder.
    use typed_builder::TypedBuilder;
    
    #[derive(TypedBuilder)]
    #[builder(builder_type(attributes(#[derive(Debug)])))]
    struct Foo {
        x: i32,
    }
  5. Configure field attributes with #[builder(...)]

    master

    You can customize how fields behave in the builder using the #[builder(...)] attribute:

    • #[builder(default)]: Makes a field optional. If not set, it uses the type's default value.
    • #[builder(default = value)]: Makes a field optional and specifies a custom default value.
    • #[builder(setter(strip_option))]: Automatically wraps the argument passed to the setter in Some(...). Useful for Option<T> fields.
    • #[builder(setter(into))]: Allows the setter to accept any type that implements Into<T> for that field.
    • #[builder(default_code = "...")]: Use this instead of default = ... if you are using other proc-macro crates that might conflict with arbitrary Rust code in default expressions.
    #[derive(TypedBuilder)]
    struct Foo {
        #[builder(default, setter(strip_option))]
        y: Option<i32>,
    
        #[builder(default=20)]
        z: i32,
    
        #[builder(setter(into))]
        name: String,
    
        #[builder(default_code = "String::from("default")")]
        label: String,
    }
  6. Define and use Mutators

    master

    Mutators are functions defined in #[builder(mutators(...))] that can mutate fields inside the builder.

    • Access: A mutator can access any field marked with #[builder(via_mutators)].
    • Requirements: To access other fields, use #[mutator(requires = [field1, field2])] on the mutator function.
    • Field Requirement: Annotating a field with #[builder(mutators(...))] makes that field required (it must be set via its setter or a mutator).
    • Behavior: Mutators do not enforce
  7. Use the `TypedBuilder` derive macro

    master

    The TypedBuilder procedural macro allows you to automatically generate a type-safe builder for your Rust structs. It is applied using the #[derive(TypedBuilder)] attribute.

    Note that TypedBuilder currently only supports structs with named fields. It does not support tuple structs, unit structs, enums, or unions.

    #[derive(TypedBuilder)]
    struct MyStruct {
        field_a: i32,
        field_b: String,
    }
  8. Customize field setters and defaults

    master

    Apply #[builder(...)] to individual fields to control how they are set and defaulted.

    Defaulting Options:

    • default: Uses Default::default(). Requires the type to implement Default.
    • default = ...: Uses a specific expression as the default.
    • default_where(...): Adds trait bounds to the default (e.g., #[builder(default, default_where(T: Default))]).
    • default_code = "...": Uses a raw string expression for the default value.
    • via_mutators: Marks the field as available for mutation via mutators.
    • via_mutators(init = ...): Initializes the field with an expression when the builder is created.

    Setter Options (setter(...)):

    • skip: Removes the setter method from the builder. Requires a default to be set.
    • into: Automatically converts the setter argument using Into.
    • strip_option: For Option<T> fields, wraps the argument in Some(...).
      • strip_option(fallback = field_opt): Adds a secondary method field_opt that accepts Option<T> directly.
      • strip_option(ignore_invalid): Prevents compile errors if used on non-Option fields.
    • strip_bool: For bool fields, the setter takes no arguments and sets the value to true. Automatically sets default to false.
    • transform = |args| expr: Allows the setter to accept different arguments and transform them into the field type.
    • prefix = "..." / suffix = "...": Wraps the setter name with a prefix or suffix.
    • mutable_during_default_resolution: Allows the field to be mutated during the evaluation of other fields' default expressions.
    use typed_builder::TypedBuilder;
    
    #[derive(TypedBuilder)]
    #[builder(field_defaults(setter(strip_option(
        ignore_invalid,
        fallback_prefix = "opt_",
        fallback_suffix = "_val"
    ))))]
    struct Foo {
        x: Option<i32>,  // Can use .x(42) or .opt_x_val(None)
        y: i32,          // Uses .y(42) only
    }