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.
Feature | bon | buildstructor | typed-builder | derive_builder |
---|---|---|---|---|
Builder for structs | ✅ | ✅ | ✅ | ✅ |
Builder for free functions | ✅ | |||
Builder for associated methods | ✅ | ✅ | ||
Panic safe | ✅ | ✅ | ✅ | build() returns a Result |
Member of Option type is optional by default | ✅ | ✅ | opt-in #[builder(default)] | opt-in #[builder(default)] |
Making required member optional is compatible by default | ✅ | ✅ | opt-in #[builder(setter(strip_option))] | opt-in #[builder(setter(strip_option))] |
Generates T::builder() method | ✅ | ✅ | ✅ | only Builder::default() |
Into conversion in setters | opt-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:
#[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:
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
.