Skip to content

Alternatives

There are several other existing alternative crates that generate builders. bon was designed with many lessons learned from them. Here is a table that compares the builder crates with some additional explanations below.

Featurebonbuildstructortyped-builderderive_builder
Builder for structs
Builder for free functions
Builder for associated methods
Panic safebuild() returns a Result
Member of Option type is optional by defaultopt-in #[builder(default)]opt-in #[builder(default)]
Making required member optional is compatible by defaultopt-in #[builder(setter(strip_option))]opt-in #[builder(setter(strip_option))]
Generates T::builder() methodonly Builder::default()
Into conversion in settersopt-in (members subset, single member)implicit (automatic)opt-in (all members + out-out, single member)opt-in (all members, single member)
impl Trait supported for functions
Anonymous lifetimes supported for functions
Self mentions in functions/structs are supported
Positional function is hidden by default
Special setter methods for collections(see below)
Custom methods can be added to the builder type✅ (mutators)
Builder may be configured to use &self/&mut self

Function builder fallback paradigm

The builder crates typed-builder and derive_builder have a bunch of attributes that allow users to insert custom behaviour into the building process of the struct. However, bon and buildstructor avoid the complexity of additional config attributes for advanced use cases by proposing the user fallback to defining a custom function with the #[builder] attached to it where it's possible to do anything you want.

However, bon still provides some simple attributes for common use cases to configure the behaviour without falling back to a more verbose syntax.

Special setter methods for collections

Other builder crates provide a way to generate methods to build collections one element at a time. For example, buildstructor even generates such methods by default:

rust
#[derive(buildstructor::Builder)]
struct User {
    friends: Vec<String>
}

fn main() {
    User::builder()
        .friend("Foo")
        .friend("Bar")
        .friend("`String` value is also accepted".to_owned())
        .build();
}

TIP

Why is there an explicit main() function in this code snippet 🤔? It's a long story explained in a blog post (feel free to skip).

This feature isn't available today in bon, but it's planned for the future. However, it won't be enabled by default, but rather be opt-in like it is in derive_builder.

The problem with this feature is that a setter that pushes an element into a collection like that may confuse the reader in case if only one element is pushed. This may hide the fact that the member is actually a collection called friends in the plural. However, this feature is still useful to provide backwards compatibility when changing the type of a member from T or Option<T> to Collection<T>.

Alternatively, bon provides a separate solution. bon exposes the following macros that provide convenient syntax to create collections.

Vec<T>[T; N]*Map<K, V>*Set<K, V>
bon::vec![]bon::arr![]bon::map!{}bon::set![]

These macros share a common feature that every element of the collection is converted with Into to shorten the syntax if you, for example, need to initialize a Vec<String> with items of type &str. Use these macros only if you need this behaviour, or ignore them if you want to be explicit in code and avoid implicit Into conversions.

Example:

rust
use bon::Builder;

#[derive(Builder)]
struct User {
    friends: Vec<String>
}

User::builder()
    .friends(bon::vec![
      "Foo",
      "Bar",
      "`String` value is also accepted".to_owned(),
    ])
    .build();

Another difference is that fields of collection types are considered required by default, which isn't the case in buildstructor.