Skip to content

Fallible Builders

With bon, you can write a builder that validates its inputs and returns a Result. It's possible to do this via the function or associated method syntax. Simply write a constructor function with the Result return type and add a #[builder] to it.

rust
use anyhow::Error;
use bon::bon;

struct User {
    id: u32,
    name: String,
}

#[bon]
impl User {
    #[builder]
    fn new(id: u32, name: String) -> Result<Self, Error> {
        if name.is_empty() {
            return Err(anyhow::anyhow!("Empty name is disallowed (user id: {id})"));
        }

        Ok(Self { id, name })
    }
}

// The `build()` method returns a `Result`
let result = User::builder()
    .id(42)
    .name(String::new())
    .build();

if let Err(error) = result {
    // Handle the error
}

With this approach, the finishing function of the generated builder returns a Result. Thus, validations are deferred until you invoke the finishing build() or call().

Fallible Setter

You can do validations earlier instead, right when the setter is called. Use #[builder(with)] with a fallible closure to achieve that. The following example is an excerpt from that attribute's API reference, see more details there in the Fallible Closure section.

rust
use bon::Builder;
use std::num::ParseIntError;

#[derive(Builder)]
struct Example {
    #[builder(with = |string: &str| -> Result<_, ParseIntError> { 
        string.parse()                                            
    })]                                                           
    x1: u32,                                                      
}

fn main() -> Result<(), ParseIntError> {
    Example::builder()
        .x1("99")? // <-- the setter returns a `Result`
        .build();

    Ok(())
}
rust
use bon::builder;
use std::num::ParseIntError;

#[builder]
fn example(
    #[builder(with = |string: &str| -> Result<_, ParseIntError> { 
        string.parse()                                            
    })]                                                           
    x1: u32,                                                      
) -> u32 {
    x1
}

fn main() -> Result<(), ParseIntError> {
    example()
        .x1("99")? // <-- the setter returns a `Result`
        .call();

    Ok(())
}
rust
use bon::bon;
use std::num::ParseIntError;


struct Example;

#[bon]
impl Example {
    #[builder]
    fn example(
        #[builder(with = |string: &str| -> Result<_, ParseIntError> { 
            string.parse()                                            
        })]                                                           
        x1: u32,                                                      
    ) -> u32 {
        x1
    }
}

fn main() -> Result<(), ParseIntError> {
    Example::example()
        .x1("99")? // <-- the setter returns a `Result`
        .call();

    Ok(())
}

None Of This Works. Help!

This is very, very(!) unlikely but if you have an elaborate use case where none of the options above are flexible enough, then your last resort is writing a custom method on the builder. You'll need to study the builder's Typestate API to be able to do that. Don't worry, it's rather simple, and you'll gain a lot of power at the end of the day 🐱.

Future Possibilities

If you have some design ideas for an attributes API to do validations with the builder macros, then feel free to post them in this Github issue.