Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions bon-macros/src/builder/builder_gen/input_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,7 @@ impl FinishFuncBody for FnCallBody {
let prefix = self
.sig
.receiver()
.map(|receiver| {
let self_token = &receiver.self_token;
quote!(#self_token.__private_receiver.)
})
.map(|_| quote!(self.__private_receiver.))
.or_else(|| {
let self_ty = &self.impl_ctx.as_deref()?.self_ty;
Some(quote!(<#self_ty>::))
Expand Down
5 changes: 3 additions & 2 deletions e2e-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ workspace = true
bon = { path = "../bon" }

[dev-dependencies]
anyhow = "1.0"
buildstructor = "0.5"
anyhow = "1.0"
buildstructor = "0.5"
macro_rules_attribute = "0.2"

[build-dependencies]
itertools = "0.13"
Expand Down
4 changes: 4 additions & 0 deletions website/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export default defineConfig({
text: "Into Conversions In-Depth",
link: "/guide/patterns/into-conversions-in-depth",
},
{
text: "Shared Configuration",
link: "/guide/patterns/shared-configuration",
}
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion website/guide/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ If your existing code defines functions with positional parameters in its public

See [this attribute's docs](../reference/builder#expose-positional-fn) for details.

**Example**:
**Example:**

```rust ignore
use bon::builder;
Expand Down
105 changes: 105 additions & 0 deletions website/guide/patterns/shared-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
outline: deep
---

# Shared Configuration

On this page, you'll learn how to share common configurations for builders to avoid code duplication.

## Problem statement

As an example, let's suppose you want to enable [`Into` conversions](./into-conversions-in-depth) for specific types across all your builders and maybe also override the name of the finishing function that consumes the builder from the default `build` to `finish`. The problem that you'll quickly run into is that you'll need to repeat the same configuration everywhere:

```rust
use bon::Builder;

#[derive(Builder)]
#[builder(
on(String, into), // [!code highlight]
on(Box<_>, into), // [!code highlight]
finish_fn = finish, // [!code highlight]
)]
struct MyLovelyStruct1 { /**/ }


#[derive(Builder)]
#[builder(
on(String, into), // [!code highlight]
on(Box<_>, into), // [!code highlight]
finish_fn = finish, // [!code highlight]
)]
struct MyLovelyStruct2 { /**/ }
```

::: tip

This code uses the [`#[builder(on(...))]`](../../reference/builder#on) attribute to configure the types of members for which `bon` should enable `Into` conversions.

:::

The annoying thing here is that we need to copy all these configurations on every struct where we derive the builder.

## Solution

### Structs

To overcome this problem we can utilize the [`macro_rules_attribute`] crate. It allows you to declare an [`attribute_alias`](https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/macro.attribute_alias.html) that defines all the shared configuration for your builders and makes it reusable.

So with the [`macro_rules_attribute`] your code will look like this:

```rust
use macro_rules_attribute::{attribute_alias, apply};

// The alias can also be defined in a separate module.
// Under the hood it creates a macro with `pub(crate)` visibility.
attribute_alias! {
#[apply(derive_builder!)] =
#[derive(::bon::Builder)]
#[builder(
on(String, into), // [!code highlight]
on(Box<_>, into), // [!code highlight]
finish_fn = finish, // [!code highlight]
)];
}

#[apply(derive_builder!)]
struct MyLovelyStruct1 { /**/ }

#[apply(derive_builder!)]
struct MyLovelyStruct2 { /**/ }
```

Use this approach if you have a lot of structs in your crate that need a builder. Adding [`macro_rules_attribute`] to your dependencies shouldn't have a noticeable impact on the compilation performance. This approach [was tested](https://github.com/ayrat555/frankenstein/blob/91ac379a52ed716e09632f78b984852c85f2adaa/src/macros.rs#L3-L14) on a crate with ~320 structs that derive a builder and compile time was the same as before adding the [`macro_rules_attribute`] crate.

### Free functions

A similar approach works with `#[bon::builder]` on free functions.
**Example:**

```rust

use macro_rules_attribute::{attribute_alias, apply};

attribute_alias! {
#[apply(builder!)] =
#[::bon::builder(
on(String, into), // [!code highlight]
on(Box<_>, into), // [!code highlight]
finish_fn = finish, // [!code highlight]
)];
}

#[apply(builder!)]
fn my_lovely_fn1(/**/) { /**/ }

#[apply(builder!)]
fn my_lovely_fn2(/**/) { /**/ }
```

### Associated methods

Unfortunately, this technique doesn't quite work with associated methods (functions inside impl blocks) due to the limitations of proc macro attribute expansion order. The `#[bon]` macro on top of the impl block is expanded first before the `#[apply(...)]` macro inside of the impl block, so `#[bon]` doesn't see the configuration expanded from the `#[apply(...)]`.

There is a proposed solution to this problem in the issue [#elastio/bon#144](https://github.com/elastio/bon/issues/144). Add a 👍 to that issue if your use case needs a solution for this, and maybe leave a comment about your particular use case where you'd like to have this feature.

[`macro_rules_attribute`]: https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/
4 changes: 2 additions & 2 deletions website/reference/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,7 @@ Don't confuse this with the top-level [`#[builder(start_fn = ...)]`](#start-fn)

:::

**Example**:
**Example:**

::: code-group

Expand Down Expand Up @@ -1224,7 +1224,7 @@ Don't confuse this with the top-level [`#[builder(finish_fn = ...)]`](#finish-fn

:::

**Example**:
**Example:**

::: code-group

Expand Down
2 changes: 1 addition & 1 deletion website/v1/guide/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ If your existing code defines functions with positional parameters in its public

See [this attribute's docs](../reference/builder#expose-positional-fn) for details.

**Example**:
**Example:**

```rust ignore
use bon::builder;
Expand Down