Limitations
Every tool has its constraints, and bon
is not an exception. The limitations described below shouldn't generally occur in your day-to-day code, and if they do, there are ways to work around them. If you feel that some of the limitations are unacceptable, feel free to open an issue to ask for some of them to be relaxed.
Intra-doc links to Self
on setter methods
Documentation placed on the original function arguments or struct fields is copied verbatim to the documentation on the generated setter methods of the builder struct. The shortcoming of this approach is that references to Self
break when moved into the impl
block of the generated builder struct.
bon
checks for the presence of [`Self`]
and [Self]
in the documentation on the function arguments and struct fields. If there are any, then a compile error suggesting to use the full type name will be generated.
The following example doesn't compile with the reference to [`Self`]
. The fix is to replace that reference with the actual name of the struct [`Foo`]
.
use bon::bon;
struct Foo;
#[bon]
impl Foo {
#[builder]
fn promote(
/// Promotes [`Self`] to the level specified in this argument
/// Promotes [`Foo`] to the level specified in this argument
new_level: String
) {}
}
Implicit generic lifetimes
Rust allows omitting generic lifetime parameters of a type in function parameters.
Example:
use bon::builder;
struct User<'a> {
name: &'a str
}
#[builder]
fn example(value: User) {}
In this example, the type User
is referenced in the function argument without lifetime parameters. This breaks the logic of the #[builder]
macro. It must know all the lifetime parameters involved in the function signature to properly generate lifetime parameters for the builder struct.
Unfortunately, macros in Rust don't have access to semantic information. All that macros see is just a sequence of syntax tokens. All that the #[builder]
macro sees in the example above is just this:
fn example(value: User) {}
This means the #[builder]
macro thinks as if there are no lifetime parameters in the User
type, and thus it generates the code that doesn't compile.
To fix this, we need to make it clear to the #[builder]
macro that User
expects some lifetime parameters. This can be done like this:
#[builder]
fn example(value: User) {}
fn example(value: User<'_>) {}
If you want to make sure your code doesn't accidentally omit a generic lifetime parameter you may enable the rustc
lint called elided_lifetimes_in_paths
. This lint is allow
by default, you can enable it in your Cargo.toml
like this:
[package.lints.rust]
elided_lifetimes_in_paths = "warn"
Destructuring patterns in function parameters
When #[builder]
is placed on a function (including associated methods), then the parameters of that function must be simple identifiers. Destructuring patterns aren't supported because it's not obvious what identifier to assign to the member in this case. This identifier will appear in #[builder(default = ...)]
expressions, for example.
Instead, if you need to destructure your function parameter, just do that inside of the function's body.
Example:
use bon::builder;
#[builder]
fn example(point: (u32, u32)) {
let (x, y) = point;
}
example()
.point((1, 2))
.call();
Formatting of attributes on function arguments
At the time of this writing, rustfmt
does a fairly bad job of formatting attributes placed on function arguments. Here is an example of rustfmt
-formatted code that uses #[bon::builder]
:
#[bon::builder]
fn example(
#[builder(default = 1)] foo: u32,
bar: u32,
fizz: u32,
) {
}
The attribute on the function's parameter was formatted on the same line with the parameter itself, even though the signature of the function already takes up multiple lines. It is harder to read the signature this way because the names of function parameters aren't aligned.
As a workaround, you can place a dummy line comment right after the attribute to prevent rustfmt
from placing the attribute on the same line with the function parameter:
Example:
#[bon::builder]
fn example(
#[builder(default = 1)] //
foo: u32,
bar: u32,
fizz: u32,
) {
}
Another workaround for this is to write a doc comment on top of the function argument:
#[bon::builder]
fn example(
/// Doc comment fixes formatting
#[builder(default = 1)]
foo: u32,
bar: u32,
fizz: u32,
) {
}
Here is the related issue in rustfmt
about this problem.
const
functions
It's possible to place #[builder]
on top of a const fn
, but the generated builder methods won't be marked const
. They use the non-const method Into::into
to transition between type states. Except for that, the generated code should be const
-compatible.
If you have a strong use case that requires full support for const
, feel free to open an issue. We'll figure something out for sure 🐱.