Overview
bon
is a Rust crate for generating compile-time-checked builders for functions and structs.
Add this to your Cargo.toml
to use it:
[dependencies]
bon = "1.2"
TIP
You can opt out of std
and alloc
cargo features with default-features = false
for no_std
environments.
Builder for a function
bon
can turn a function with positional parameters into a function with "named" parameters via a builder. It's as easy as placing the #[builder]
macro on top of it.
Example:
use bon::builder;
#[builder]
fn greet(name: &str, age: u32) -> String {
format!("Hello {name} with age {age}!")
}
let greeting = greet()
.name("Bon")
.age(24)
.call();
assert_eq!(greeting, "Hello Bon with age 24!");
TIP
Many things are customizable with additional attributes. #[builder]
macro reference describes all of them.
Almost any syntax is supported including async
, fallible functions, impl Trait
, etc.
Builder for an associated method
You can also generate a builder for associated methods. For this to work you need to add a #[bon]
macro on top of the impl
block additionally.
Example:
use bon::bon;
struct Counter {
val: u32,
}
#[bon] // <- this macro is required on the impl block
impl Counter {
#[builder]
fn new(initial: Option<u32>) -> Self {
Self {
val: initial.unwrap_or_default(),
}
}
#[builder]
fn increment(&mut self, diff: u32) {
self.val += diff;
}
}
let mut counter = Counter::builder()
.initial(3)
.build();
counter
.increment()
.diff(3)
.call();
assert_eq!(counter.val, 6);
Why is that #[bon]
macro on top of the impl
block required? 🤔 (feel free to skip)
There are a couple of technical reasons.
First of all, it's the lack of surrounding context given to a proc macro in Rust. A proc macro sees only the syntax it is placed on top of. For example, the #[builder]
macro inside of the impl
block can't see the impl Counter
part of the impl block above it. However, it needs that information to tell the actual type of Self
.
Second, the #[builder]
proc macro generates new items such as the builder struct type definition, which it needs to output adjacently to the impl
block itself. However, proc macros in Rust can only modify the part of the syntax they are placed on and generate new items on the same level of nesting. The #[builder]
macro inside of the impl
block can't just break out of it.
Why does it compile without an import of bon::builder
? 🤔 (feel free to skip)
This is because there is no separate #[builder]
proc macro running in this case. Only the #[bon]
macro handles code generation, it's an active attribute, while #[builder]
is a dumb inert data attribute (see the Rust Reference for details about active and inert attributes).
It wouldn't harm if bon::builder
was imported. It won't shadow the inert #[builder]
attribute, but the import of that macro will be reported as unused by the compiler.
To follow the usual Rust builder naming conventions bon
treats the method named new
inside of the impl block specially. It generates functions with a bit different names.
If #[builder]
is placed on the method called new
, then the generated functions are called:
Start function | Finish function |
---|---|
builder() -> {T}Builder | build(self) -> T |
For any other methods not called new
and for any free function the naming is a bit different:
Start function | Finish function |
---|---|
{fn_name}() -> {T}{PascalCaseFnName}Builder | call(self) -> T |
Builder for a struct
bon
supports the classic pattern of annotating a struct to generate a builder.
Example:
use bon::builder;
#[builder]
struct User {
id: u32,
name: String,
}
let user = User::builder()
.id(1)
.name("Bon")
.build();
assert_eq!(user.id, 1);
assert_eq!(user.name, "Bon");
TIP
#[builder]
on a struct generates builder API that is fully compatible with placing #[builder]
on the new()
method with a signature similar to the struct's fields.
See compatibility page for details.
No panics possible
The builders generated by #[builder]
use the typestate pattern to make sure all required parameters are filled and setter methods aren't called repeatedly to prevent unintentional overwrites and typos. If something is wrong, a compile error will be created. There are no potential panics and unwrap()
calls inside of the builder.
Everything you want is already the default
The generated builders provide ergonomic API by default. You usually won't need to override anything.
Option<T>
makes the setter optional
If your function argument or struct field (or member for short) is of type Option<T>
, then the generated builder will not enforce setting a value for this member, defaulting to None
.
It also generates two setters: one accepts T
and the other accepts Option<T>
. The first avoids wrapping values with Some()
on the call site. The second allows passing the Option<T>
value directly.
See optional members page for details.
Automatic Into
conversions
If your function argument or struct field is a String
, then you'd like to be able to pass &str
to the setter. To do this you'd want the setter to accept a parameter of type impl Into<String>
.
bon
does that by default not only for String
. You may've seen this in action in the example code snippets above where we don't call .to_owned()
or .to_string()
on string literals.
See Into
conversions for details.
Supported syntax for functions
The #[builder]
attribute works almost with any kind of function that uses any available Rust syntax. All of the following is supported.
- Functions can return any values including
Result
,Option
, etc. - The
impl Trait
syntax is supported both in function parameters and return type. async
functions.unsafe
functions.- Generic type parameters.
- Generic const parameters (const generics).
- Generic lifetimes.
where
clauses.- Anonymous lifetimes, i.e.
'_
or just regular references without explicit lifetimes like&u32
. - Nested functions defined inside of other items bodies, e.g.rust
fn foo() { // Just works #[bon::builder] fn bar() {} }
What's next?
TIP
If you like the idea of this crate and want to say "thank you" or "keep up doing this" consider giving us a star on Github. Any support and contribution are appreciated 🐱!
You may consider reading the rest of the Guide
section to harness the full power of bon
and understand the decisions it makes. However, feel free to skip the docs and just use the #[builder]
macro in your code. It's designed to be intuitive, so it'll probably do the thing you want it to do already.
If you can't figure something out, consult the docs and maybe use that search 🔍 Search
thing at the top to navigate. You may also create an issue in the Github repository for help.
Acknowledgments
This project was heavily inspired by such awesome crates as buildstructor
, typed-builder
and derive_builder
. This crate was designed with many lessons learned from them.
See alternatives for comparison.