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
2 changes: 1 addition & 1 deletion website/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export default defineConfig({
link: "/reference/builder/top-level/finish_fn",
},
{
text: "generics",
text: "generics 🔬",
link: "/reference/builder/top-level/generics",
},
{
Expand Down
2 changes: 2 additions & 0 deletions website/src/guide/patterns/optional-generic-members.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,5 @@ fn bad<T: Into<String>>(x1: Option<T>) {
So, the builder has to carry the generic parameter `T` in its type for you to be able to use that type in your function body to merely invoke `Into::into` on a parameter. Instead, strive to do such conversions in setters.

If you are doing a conversion with something other than `Into`, then use [`#[builder(with)]`](../../reference/builder/member/with) to apply the same technique for getting rid of generic parameters _early_.

See also another, more complex solution to this problem with [`#[builder(generics(setters))]`](../../reference/builder/top-level/generics#no-turbofish-optional-generic-members).
1 change: 1 addition & 0 deletions website/src/reference/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ These attributes are placed on top of a `struct` or `fn` declaration.
| [`crate`](./builder/top-level/crate) | Overrides path to `bon` crate referenced in the generated code |
| [`derive`](./builder/top-level/derive) | Generates additional derives for the builder struct itself |
| [`finish_fn`](./builder/top-level/finish_fn) | Overrides name, visibility and docs for the finishing function |
| [`generics` 🔬](./builder/top-level/generics) | Generates methods to overwrite generic type parameters |
| [`on`](./builder/top-level/on) | Applies member attributes to all members matching a type pattern |
| [`start_fn`](./builder/top-level/start_fn) | Overrides name, visibility and docs for the starting function |
| [`state_mod`](./builder/top-level/state_mod) | Overrides name, visibility and docs for the builder's [typestate API](../guide/typestate-api) module |
Expand Down
243 changes: 157 additions & 86 deletions website/src/reference/builder/top-level/generics.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,14 @@
# `generics`
# `generics` :microscope:

**Applies to:** <Badge text="structs"/> <Badge text="functions"/> <Badge text="methods"/>

::: warning 🔬 Experimental
::: danger 🔬 **Experimental**

This attribute is experimental and requires the `experimental-generics-setters` cargo feature to be enabled. The API may change in minor releases.
This attribute is available under the cargo feature `experimental-generics-setters`. Breaking changes may occur between **minor** releases but not between patch releases.

:::

Generates methods on the builder for converting generic type parameters to different types. This is useful when building up values with different types at different stages of construction.

**Syntax:**

```attr
#[builder(generics(setters(...)))]
```

## `setters`

Configures the generic parameter conversion methods.

**Short syntax** configures just the name pattern:

```attr
#[builder(generics(setters = "conv_{}"))]
```

**Long syntax** provides more flexibility. The `name` parameter is required, while others are optional:

```attr
#[builder(
generics(setters(
name = "conv_{}",
vis = "pub(crate)",
doc {
/// Custom documentation
}
))
)]
```

### `name`

**Required.** A pattern string where `{}` will be replaced with the `snake_case` name of each generic parameter. For example, with generic parameters `<TData, TError>` and pattern `"with_{}"`, methods `with_t_data()` and `with_t_error()` will be generated.

### `vis`

The visibility for the generated conversion methods. Must be enclosed with quotes. Use `""` or [`"pub(self)"`](https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself) for private visibility.

The default visibility matches the visibility of the [`builder_type`](./builder_type#vis).

### `doc`

Custom documentation for the generated conversion methods. The syntax expects a block with doc comments:

```attr
doc {
/// Custom documentation
}
```

Simple documentation is generated by default explaining what the method does.
Generates methods to overwrite generic type parameters. This feature is useful for type-level state machines: starting with marker types and changing them as you progress through states. See examples below for inspiration.

## How It Works

Expand All @@ -71,73 +19,196 @@ For each generic type parameter (not lifetimes or const generics), a conversion

This allows you to start with placeholder types (like `()`) and convert them to concrete types as you build.

## Example
## Basic Example

::: code-group

```rust [Basic]
```rust [Struct]
use bon::Builder;

#[derive(Builder)]
#[builder(generics(setters = "with_{}"))] // [!code highlight]
struct Container<TData, TError> {
struct Example<TData> {
data: TData,
error: TError,
count: u32,
}

// Start with unit types, then convert to concrete types
let container = Container::<(), ()>::builder()
let example = Example::<()>::builder()
.count(42)
.with_t_data::<i32>() // Convert TData from () to i32 // [!code highlight]
// Change TData from () to i32. Only possible if none // [!code highlight]
// of the members that use `TData` generic are set // [!code highlight]
.with_t_data::<i32>()
.data(100)
.with_t_error::<&str>() // Convert TError from () to &str // [!code highlight]
.error("error message")
.build();

assert_eq!(container.data, 100);
assert_eq!(container.error, "error message");
assert_eq!(container.count, 42);
assert_eq!(example.data, 100_i32);
assert_eq!(example.count, 42_u32);
```

```rust [Custom Methods]
```rust [Function]
use bon::builder;

#[builder(generics(setters = "with_{}"))] // [!code highlight]
fn example<TData>(
data: TData,
count: u32
) -> (TData, u32) {
(data, count)
}

// Start with unit types, then convert to concrete types
let example = example::<()>()
.count(42)
// Change TData from () to i32. Only possible if none // [!code highlight]
// of the members that use `TData` generic are set // [!code highlight]
.with_t_data::<i32>()
.data(100)
.call();

assert_eq!(example.0, 100_i32);
assert_eq!(example.1, 42_u32);
```

```rust [Method]
use bon::bon;

struct Example;

#[bon]
impl Example {
#[builder(generics(setters = "with_{}"))] // [!code highlight]
fn example<TData>(
data: TData,
count: u32
) -> (TData, u32) {
(data, count)
}
}

// Start with unit types, then convert to concrete types
let example = Example::example::<()>()
.count(42)
// Change TData from () to i32. Only possible if none // [!code highlight]
// of the members that use `TData` generic are set // [!code highlight]
.with_t_data::<i32>()
.data(100)
.call();

assert_eq!(example.0, 100_i32);
assert_eq!(example.1, 42_u32);
```

:::

## No-Turbofish Optional Generic Members

This feature can be used to provide default values for generic types that can be overwritten with a setter.
This is one of the solutions for the [optional generic members turbofish problem](../../../guide/patterns/optional-generic-members).
It's quite verbose at definition site, so it should be used as a last resort. You may also wrap this pattern with your own macro if you are going to use it alot.

::: tip NOTE

This example defines [custom methods](../../../guide/typestate-api/custom-methods). Consult the referenced guide page for details if needed.

:::

::: code-group

```rust
use bon::Builder;

#[derive(Builder)]
#[builder(generics(setters = "conv_{}"))] // [!code highlight]
#[builder(
generics(setters = "with_{}"), // [!code highlight]
// Privatize the starting function, we'll define our own
start_fn(vis = "", name = builder_internal),
)]
struct Data<T> {
value: T,
// Privatize the setters, we'll define our own
#[builder(setters(name = value_internal, vis = ""))]
value: Option<T>,
}

// Use the generated conversion method to implement custom logic
impl<T1, S: data_builder::State> DataBuilder<T1, S> {
fn convert_and_set<T2>(
self,
value: T2
) -> DataBuilder<T2, data_builder::SetValue<S>>
// Custom `builder()` method that begins with `T = ()`
impl Data<()> {
pub fn builder() -> DataBuilder<()> {
Data::builder_internal()
}
}

use data_builder::{State, SetValue, IsUnset};

// Custom setter, that overwrites `T` with the type of the provided value.
impl<T, S: State> DataBuilder<T, S> {
fn value<NewT>(self, value: NewT) -> DataBuilder<NewT, SetValue<S>>
where
S::Value: data_builder::IsUnset,
S::Value: IsUnset,
{
self.conv_t().value(value) // [!code highlight]
self.with_t().value_internal(value) // [!code highlight]
}
}

let data = Data::<()>::builder()
.convert_and_set(42)
// By default, the generic parameter is `()`. No type hints are required.
let data_unit = Data::builder().build();
assert_eq!(data_unit.value, None);

let data_u32 = Data::builder()
.value(42)
.build();

assert_eq!(data.value, 42);
assert_eq!(data_u32.value, Some(42));
```

:::

## Use Cases
## Config

### `setters`

Configures the generic parameter overwrite methods.

**Short syntax** configures just the name pattern:

```attr
#[builder(generics(setters = "with_{}"))]
```

**Long syntax** provides more flexibility. The `name` parameter is required, while others are optional:

```attr
#[builder(
generics(setters(
name = "with_{}",
vis = "pub(crate)",
doc {
/// Custom documentation
}
))
)]
```

#### `name`

**Required.** A pattern string where `{}` will be replaced with the `snake_case` name of each generic parameter. For example, with generic types `<TData, TError>` and pattern `"with_{}"`, methods `with_t_data()` and `with_t_error()` will be generated.

This feature is particularly useful for:
#### `vis`

- **Type-level state machines**: Starting with marker types and converting them as you progress through states
- **Builder patterns with type parameters**: When you need to build complex generic structs step-by-step
- **API design**: Allowing users to specify types incrementally rather than all at once
The visibility for the generated conversion methods. Must be enclosed with quotes. Use `""` or [`"pub(self)"`](https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself) for private visibility.

The default visibility matches the visibility of the [`builder_type`](./builder_type#vis).

#### `doc`

Custom documentation for the generated conversion methods. The syntax expects a block with doc comments:

```attr
doc {
/// Custom documentation
}
```

Simple documentation is generated by default explaining what the method does.

## See Also

Expand Down