Skip to content
WARNING

You are viewing the docs for an older major version of bon (v1).

Click here to view the docs for the latest version (v3).

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:

toml
[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:

rust
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:

rust
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 functionFinish function
builder() -> {T}Builderbuild(self) -> T

For any other methods not called new and for any free function the naming is a bit different:

Start functionFinish function
{fn_name}() -> {T}{PascalCaseFnName}Buildercall(self) -> T

Builder for a struct

bon supports the classic pattern of annotating a struct to generate a builder.

Example:

rust
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.