From 037f30e8c58bd9eef3a50ed70e8ed422db255c39 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 12 Feb 2026 23:40:53 +0000 Subject: [PATCH 1/3] Improve docs for `generics(setters)` --- website/.vitepress/config.mts | 2 +- .../patterns/optional-generic-members.md | 2 + website/src/reference/builder.md | 1 + .../reference/builder/top-level/generics.md | 243 +++++++++++------- 4 files changed, 161 insertions(+), 87 deletions(-) diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index c50dc2c5..6b550ede 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -285,7 +285,7 @@ export default defineConfig({ link: "/reference/builder/top-level/finish_fn", }, { - text: "generics", + text: "generics 🔬", link: "/reference/builder/top-level/generics", }, { diff --git a/website/src/guide/patterns/optional-generic-members.md b/website/src/guide/patterns/optional-generic-members.md index e0eb44ad..2cf24f64 100644 --- a/website/src/guide/patterns/optional-generic-members.md +++ b/website/src/guide/patterns/optional-generic-members.md @@ -112,3 +112,5 @@ fn bad>(x1: Option) { 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). diff --git a/website/src/reference/builder.md b/website/src/reference/builder.md index 689547f4..0cc452f4 100644 --- a/website/src/reference/builder.md +++ b/website/src/reference/builder.md @@ -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 | diff --git a/website/src/reference/builder/top-level/generics.md b/website/src/reference/builder/top-level/generics.md index dcd41ebc..44342e0a 100644 --- a/website/src/reference/builder/top-level/generics.md +++ b/website/src/reference/builder/top-level/generics.md @@ -1,66 +1,14 @@ -# `generics` +# `generics` 🔬 **Applies to:** -::: 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 `` 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 @@ -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 { +struct Example { 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::() // 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::() .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( + 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::() + .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( + 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::() + .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](http://localhost:5173/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 { - value: T, + // Privatize the setters, we'll define our own + #[builder(setters(name = value_internal, vis = ""))] + value: Option, } -// Use the generated conversion method to implement custom logic -impl DataBuilder { - fn convert_and_set( - self, - value: T2 - ) -> DataBuilder> +// 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 DataBuilder { + fn value(self, value: NewT) -> DataBuilder> 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 `` 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 From 06121fb41b295fa25ee1c0d84cb5c4c66f07130c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 12 Feb 2026 23:42:57 +0000 Subject: [PATCH 2/3] Fix link --- website/src/reference/builder/top-level/generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/reference/builder/top-level/generics.md b/website/src/reference/builder/top-level/generics.md index 44342e0a..25f703f6 100644 --- a/website/src/reference/builder/top-level/generics.md +++ b/website/src/reference/builder/top-level/generics.md @@ -104,7 +104,7 @@ 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](http://localhost:5173/guide/patterns/optional-generic-members). +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 From 20bc4cf8377f1b236d09b089d90e4beab19d82e0 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 12 Feb 2026 23:49:17 +0000 Subject: [PATCH 3/3] Fixes --- website/src/reference/builder/top-level/generics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/reference/builder/top-level/generics.md b/website/src/reference/builder/top-level/generics.md index 25f703f6..35f7c78f 100644 --- a/website/src/reference/builder/top-level/generics.md +++ b/website/src/reference/builder/top-level/generics.md @@ -1,8 +1,8 @@ -# `generics` 🔬 +# `generics` :microscope: **Applies to:** -::: danger 🔬 Experimental +::: danger 🔬 **Experimental** This attribute is available under the cargo feature `experimental-generics-setters`. Breaking changes may occur between **minor** releases but not between patch releases.