Skip to content

Next-gen builder macro Bon 2.3 release 🎉. Positional arguments in starting and finishing functions 🚀

2024-09-14

bon is a Rust crate for generating compile-time-checked builders for functions and structs. It also provides idiomatic partial application with optional and named parameters for functions and methods.

If you don't know about bon, then see the motivational blog post and the crate overview.

Meme of this release 🐱

New features

Positional arguments in starting and finishing functions

While having the ability to use separate setters for the members gives you a ton of flexibility and extensibility described on the "Compatibility" page, sometimes you don't need all of that.

Maybe you'd like to pick out some specific members and let the user pass their values as positional parameters to the starting function that creates the builder or to the finishing function that consumes it. This reduces the syntax a bit at the cost of some extensibility loss ⚖️, but it may be worth it!

Starting function

As an example, suppose we have a Treasure struct with x and y coordinates and a label that describes the payload of the treasure. Since all treasures are located somewhere, they all have coordinates, and it would be cool to specify them in a single starting function call.

To do that we can use the #[builder(start_fn)] attribute. There are two contexts where we can place it, and they both have a different meaning:

We'll want to use both of these attributes in our example to give a better name for the starting function that describes its inputs and configure x and y as positional parameters on the starting function as well.

Example:

rust
use bon::Builder;

#[derive(Builder)]
// Top-level attribute to give a better name for the starting function
#[builder(start_fn = with_coordinates)]                                
struct Treasure {
    // Member-level attribute to mark the member as
    // a parameter of `with_coordinates()`
    #[builder(start_fn)]                            
    x: u32,

    #[builder(start_fn)] 
    y: u32,

    label: Option<String>,
}

let treasure = Treasure::with_coordinates(2, 9) 
    .label("oats".to_owned())
    .build();

assert_eq!(treasure.x, 2);
assert_eq!(treasure.y, 9);
assert_eq!(treasure.label.as_deref(), Some("oats"));

Here, the generated with_coordinates method has the following signature:

rust
impl Treasure {
    fn with_coordinates(x: u32, y: u32) -> TreasureBuilder { /**/ }
}

Finishing function

Now let's say we need to know the person who claimed the Treasure. While describing the treasure using the current builder syntax we'd like the person who claimed it to specify their first name and last name at the end of the building process.

We can use a similar combination of the top-level #[builder(finish_fn = ...)] and the member-level #[builder(finish_fn)] attributes to do that.

Example:

rust
use bon::Builder;

#[derive(Builder)]
#[builder(start_fn = with_coordinates)]
#[builder(finish_fn = claim)]  
struct Treasure {
    #[builder(start_fn)]
    x: u32,

    #[builder(start_fn)]
    y: u32,

    #[builder(finish_fn)]          
    claimed_by_first_name: String, 

    #[builder(finish_fn)]          
    claimed_by_last_name: String,  

    label: Option<String>,
}

let treasure = Treasure::with_coordinates(2, 9)
    .label("oats".to_owned())
    .claim("Lyra".to_owned(), "Heartstrings".to_owned()); 

assert_eq!(treasure.x, 2);
assert_eq!(treasure.y, 9);
assert_eq!(treasure.label.as_deref(), Some("oats"));
assert_eq!(treasure.claimed_by_first_name, "Lyra");        
assert_eq!(treasure.claimed_by_last_name, "Heartstrings"); 

You may also combine these attributes with #[builder(into)] or #[builder(on(..., into))] to reduce the number of to_owned() calls a bit. See this described in detail on the new "Positional members" page in the guide.

Guaranteed MSRV is 1.59.0 now

On the previous week's update (2.2 release) a promise was made to reduce the MSRV (minimum supported Rust version) from the initial 1.70.0 even further, and this has been done 🎉!

This is the lowest possible MSRV we can guarantee for now. The choice of this version was made based on our design requirements for const generics supports described in the comment here.

Deprecation warnings

As was promised in the previous release we are enabling deprecation warnings for the usage of the bare #[bon::builder] attribute on structs in favour of the new #[derive(bon::Builder)] syntax.

The #[builder] syntax is still supported on functions and associated methods, and it's the only way to generate builders for them.

The reasons for this deprecation as well as the instruction to update your code are described in the 2.2. release blog post.

WARNING

This isn't a breaking change, and the code that uses #[bon::builder] on a struct will still compile albeit with a compiler warning. Once bon reaches a 3.0 release we'll remove support for #[bon::builder] on structs entirely. However, there are no particular reasons and plans for a new major release of bon yet.

Summary

Huge thank you for 925 stars ⭐ on Github! Consider giving bon a star if you haven't already. Your support and feedback are a big motivation and together we can build a better builder 🐱!

Bon's goal is to empower everyone to build beautiful APIs with great flexibility and extensibility. If you have any feedback or ideas for improvement consider joining our Discord server to discuss them, or just open an issue on Github.

TIP

You can leave comments for this post on the platform of your choice:

Veetaha

Veetaha

Lead developer @ elastio

Creator of bon