diff --git a/website/src/guide/basics.md b/website/src/guide/basics.md index 362eacb9..54d41484 100644 --- a/website/src/guide/basics.md +++ b/website/src/guide/basics.md @@ -2,6 +2,6 @@ Welcome to the Bon Guide Book 🍬! It's meant to walk you through `bon` so you don't get lost while exploring it 🐈. -Code examples may use either `fn` or `struct` builder syntax interchangeably. Both follow the same principles unless stated otherwise. +Some code examples have tabs for "Struct", "Function" and "Method" syntax. Some examples may not have them, but it doesn't necessarily mean the described feature works only with one kind of syntax. Now, let's begin with the first topic of [Optional Members](./basics/optional-members). diff --git a/website/src/guide/basics/custom-conversions.md b/website/src/guide/basics/custom-conversions.md index a3c0db44..4ad66da3 100644 --- a/website/src/guide/basics/custom-conversions.md +++ b/website/src/guide/basics/custom-conversions.md @@ -4,15 +4,15 @@ You can pass a custom closure to `#[builder(with)]`. It will define the signature of the setter and perform a conversion. -```rust -use bon::Builder; +::: code-group +```rust [Struct] struct Point { x: u32, y: u32, } -#[derive(Builder)] +#[derive(bon::Builder)] struct Example { #[builder(with = |x: u32, y: u32| Point { x, y })] // [!code highlight] point: Point, @@ -26,6 +26,57 @@ assert_eq!(value.point.x, 2); assert_eq!(value.point.y, 3); ``` +```rust [Function] +struct Point { + x: u32, + y: u32, +} + +#[bon::builder] +fn example( + #[builder(with = |x: u32, y: u32| Point { x, y })] // [!code highlight] + point: Point, +) -> Point { + point +} + +let value = example() + .point(2, 3) // [!code highlight] + .call(); + +assert_eq!(value.x, 2); +assert_eq!(value.y, 3); +``` + +```rust [Method] +struct Point { + x: u32, + y: u32, +} + +struct Example; + +#[bon::bon] +impl Example { + #[builder] + fn example( + #[builder(with = |x: u32, y: u32| Point { x, y })] // [!code highlight] + point: Point, + ) -> Point { + point + } +} + +let value = Example::example() + .point(2, 3) // [!code highlight] + .call(); + +assert_eq!(value.x, 2); +assert_eq!(value.y, 3); +``` + +::: + You can make the setter fallible by passing a [fallible closure](../../reference/builder/member/with#fallible-closure). You can pass one of [well-known functions](../../reference/builder/member/with#well-known-functions) instead of a closure to `#[builder(with)]`. If any of them fit your use, this will save you some characters to type. diff --git a/website/src/guide/basics/derives-for-builders.md b/website/src/guide/basics/derives-for-builders.md index 2d4f3bb8..55c5874c 100644 --- a/website/src/guide/basics/derives-for-builders.md +++ b/website/src/guide/basics/derives-for-builders.md @@ -4,10 +4,35 @@ You can specify some extra derives on the generated builder struct itself via th For example, if you want to inspect the values set in the builder for debugging purposes, you can derive the `Debug` trait for your builder. -```rust -use bon::builder; +::: code-group +```rust [Struct] +#[derive(bon::Builder)] #[builder(derive(Debug))] // [!code highlight] +struct Example { + name: String, + is_admin: bool, + level: Option, +} + +let builder = Example::builder().name("Bon".to_owned()); + +// This will output the current state of the builder to `stderr` +dbg!(&builder); + +// You can also format the debug output to `String`: +assert_eq!( + format!("{builder:?}"), + // Only the fields that were set will be output + r#"ExampleBuilder { name: "Bon" }"# +); + +// Finish building +builder.is_admin(true).build(); +``` + +```rust [Function] +#[bon::builder(derive(Debug))] // [!code highlight] fn example( name: String, is_admin: bool, @@ -30,4 +55,35 @@ assert_eq!( builder.is_admin(true).call(); ``` +```rust [Method] +struct Example; + +#[bon::bon] +impl Example { + #[builder(derive(Debug))] // [!code highlight] + fn method( + name: String, + is_admin: bool, + level: Option, + ) {} +} + +let builder = Example::method().name("Bon".to_owned()); + +// This will output the current state of the builder to `stderr` +dbg!(&builder); + +// You can also format the debug output to `String`: +assert_eq!( + format!("{builder:?}"), + // Only the fields that were set will be output + r#"ExampleMethodBuilder { name: "Bon" }"# +); + +// Finish building +builder.is_admin(true).call(); +``` + +::: + You can also derive the `Clone` and `Into` traits for your builder using this same attribute. See more details in the [reference for the `#[builder(derive(...))]` attribute](../../reference/builder/top-level/derive). diff --git a/website/src/guide/basics/documenting.md b/website/src/guide/basics/documenting.md index 1724b39b..ca2daa97 100644 --- a/website/src/guide/basics/documenting.md +++ b/website/src/guide/basics/documenting.md @@ -2,13 +2,9 @@ In regular Rust, it's not possible to place doc comments on function arguments. But with `#[builder]` it is. Documentation written on the arguments will be placed on the generated setter methods. -**Example:** - ````rust -use bon::builder; - /// Function that returns a greeting special-tailored for a given person -#[builder] +#[bon::builder] fn greet( /// Name of the person to greet. /// @@ -33,26 +29,59 @@ This works because Rust compiler checks for invalid placement of `#[doc = ...]` When `#[derive(Builder)]` is placed on top of a struct, then documentation on the struct fields will be copied to the docs on the setter methods. -## Custom `doc` attributes +## Custom `doc` Attributes You can override documentation on other items generated by builder macros. There are multiple attributes accepting a `doc { ... }` block. -```rust -use bon::Builder; +::: code-group -#[derive(Builder)] +```rust [Struct] +#[derive(bon::Builder)] #[builder( builder_type(doc { /// Custom docs on the builder struct itself }), - start_fn(doc { - /// Custom docs on the starting function + finish_fn(doc { + /// Custom docs on the finishing function }), // ... )] struct Example {} ``` +```rust [Function] +#[bon::builder( + builder_type(doc { + /// Custom docs on the builder struct itself + }), + finish_fn(doc { + /// Custom docs on the finishing function + }), + // ... +)] +fn example() {} +``` + +```rust [Method] +struct Example; + +#[bon::bon] +impl Example { + #[builder( + builder_type(doc { + /// Custom docs on the builder struct itself + }), + finish_fn(doc { + /// Custom docs on the finishing function + }), + // ... + )] + fn example() {} +} +``` + +::: + You can document the following items this way: | Attribute | Documentation target | @@ -64,6 +93,6 @@ You can document the following items this way: | [`setters`](../../reference/builder/member/setters#doc) | Custom docs for setters. Prevents copying them from the field/argument | | [`getter`](../../reference/builder/member/getter#doc) | Custom docs for a getter. Prevents copying them from the field/argument | -## Positional members +## Positional Members Documentation comments are allowed on [positional members](./positional-members). However, since there are no separate setter methods generated for them, the docs on these members will not be copied anywhere, and thus they won't appear in `rustdoc`. Instead, it's recommended to write documentation for these members on the top level of the struct or function. diff --git a/website/src/guide/basics/into-conversions.md b/website/src/guide/basics/into-conversions.md index e56330af..14b6980f 100644 --- a/website/src/guide/basics/into-conversions.md +++ b/website/src/guide/basics/into-conversions.md @@ -2,36 +2,79 @@ If you have members of type `String`, or `PathBuf`, and you need to set them to a hard-coded string literal, then you have to write `.to_owned()` or `.to_string()` or `.into()`. -```rust -use bon::Builder; +::: code-group + +```rust [Struct] use std::path::PathBuf; -#[derive(Builder)] // [!code focus] -struct Project { // [!code focus] +#[derive(bon::Builder)] // [!code focus] +struct Example { // [!code focus] name: String, // [!code focus] description: String, // [!code focus] path: PathBuf, // [!code focus] } // [!code focus] -Project::builder() +Example::builder() .name("Bon".to_owned()) // [!code focus] .description("Awesome crate 🐱".to_string()) // [!code focus] .path("/path/to/bon".into()) // [!code focus] .build(); ``` +```rust [Function] +use std::path::PathBuf; + +#[bon::builder] // [!code focus] +fn example( // [!code focus] + name: String, // [!code focus] + description: String, // [!code focus] + path: PathBuf, // [!code focus] +) {} // [!code focus] + +example() + .name("Bon".to_owned()) // [!code focus] + .description("Awesome crate 🐱".to_string()) // [!code focus] + .path("/path/to/bon".into()) // [!code focus] + .call(); +``` + +```rust [Method] +use std::path::PathBuf; + +struct Example; + +#[bon::bon] +impl Example { + #[builder] // [!code focus] + fn example( // [!code focus] + name: String, // [!code focus] + description: String, // [!code focus] + path: PathBuf, // [!code focus] + ) {} // [!code focus] +} + +Example::example() + .name("Bon".to_owned()) // [!code focus] + .description("Awesome crate 🐱".to_string()) // [!code focus] + .path("/path/to/bon".into()) // [!code focus] + .call(); +``` + +::: + However, you can ask `bon` to generate setters that accept `impl Into` to remove the need for manual conversion. This can be configured with [`#[builder(into)]`](../../reference/builder/member/into) for a single member or with [`#[builder(on({type}, into))]`](../../reference/builder/top-level/on) for many members at once. -```rust -use bon::Builder; +::: code-group + +```rust [Struct] use std::path::PathBuf; // All setters for members of type `String` will accept `impl Into` // [!code highlight] -#[derive(Builder)] // [!code highlight] +#[derive(bon::Builder)] // [!code highlight] #[builder(on(String, into))] // [!code highlight] -struct Project { +struct Example { name: String, description: String, @@ -40,11 +83,60 @@ struct Project { path: PathBuf, } -Project::builder() +Example::builder() .name("Bon") // [!code highlight] .description("Awesome crate 🐱") // [!code highlight] .path("/path/to/your/heart") // [!code highlight] .build(); ``` +```rust [Function] +use std::path::PathBuf; + +// All setters for members of type `String` will accept `impl Into` // [!code highlight] +#[bon::builder(on(String, into))] // [!code highlight] +fn example( + name: String, + description: String, + + // The setter only for this member will accept `impl Into` // [!code highlight] + #[builder(into)] // [!code highlight] + path: PathBuf, +) {} + +example() + .name("Bon") // [!code highlight] + .description("Awesome crate 🐱") // [!code highlight] + .path("/path/to/your/heart") // [!code highlight] + .call(); +``` + +```rust [Method] +use std::path::PathBuf; + +struct Example; + +#[bon::bon] +impl Example { + // All setters for members of type `String` will accept `impl Into` // [!code highlight] + #[builder(on(String, into))] // [!code highlight] + fn example( + name: String, + description: String, + + // The setter only for this member will accept `impl Into` // [!code highlight] + #[builder(into)] // [!code highlight] + path: PathBuf, + ) {} +} + +Example::example() + .name("Bon") // [!code highlight] + .description("Awesome crate 🐱") // [!code highlight] + .path("/path/to/your/heart") // [!code highlight] + .call(); +``` + +::: + `Into` conversions don't always make sense, and you should be aware of their downsides as well. The article [Into Conversions In-Depth](../patterns/into-conversions-in-depth) provides recommendations on when it makes sense to use and to avoid `Into` conversions. diff --git a/website/src/guide/basics/optional-members.md b/website/src/guide/basics/optional-members.md index 3a18b7e9..e7709c14 100644 --- a/website/src/guide/basics/optional-members.md +++ b/website/src/guide/basics/optional-members.md @@ -8,7 +8,27 @@ outline: deep If your function argument or struct field (or member for short) is of type `Option`, then the generated builder will not enforce setting a value for this member, defaulting to `None`. -```rust +::: code-group + +```rust [Struct] +#[derive(bon::Builder)] +struct Example { + level: Option +} + +// We can call it without specifying the `level` +Example::builder().build(); +``` + +```rust [Function] +#[bon::builder] +fn example(level: Option) {} + +// We can call it without specifying the `level` +example().call(); +``` + +```rust [Method] #[bon::builder] fn example(level: Option) {} @@ -16,6 +36,8 @@ fn example(level: Option) {} example().call(); ``` +::: + You can use [`#[builder(required)]`](../../reference/builder/member/required) to opt-out from this. ### Setters Pair @@ -49,13 +71,37 @@ Thanks to this design, changing the member from required to optional [preserves Pass a non-`None` value via the `{member}(T)` setter: -```rust ignore +::: code-group + +```rust ignore [Struct] +Example::builder().level(42).build(); +``` + +```rust ignore [Function] example().level(42).call(); ``` +```rust ignore [Method] +Example::example().level(42).call(); +``` + +::: + Pass an `Option` value directly via the `maybe_{member}(Option)` setter: -```rust ignore +::: code-group + +```rust ignore [Struct] +let value = if some_condition { + Some(42) +} else { + None +}; + +Example::builder().maybe_level(value).build(); +``` + +```rust ignore [Function] let value = if some_condition { Some(42) } else { @@ -65,6 +111,18 @@ let value = if some_condition { example().maybe_level(value).call(); ``` +```rust ignore [Method] +let value = if some_condition { + Some(42) +} else { + None +}; + +Example::example().maybe_level(value).call(); +``` + +::: + ## `#[builder(default)]` To make a member of non-`Option` type optional you may use [`#[builder(default)]`](../../reference/builder/member/default). This attribute uses the [`Default`](https://doc.rust-lang.org/stable/std/default/trait.Default.html) trait or the provided expression to assign the default value for the member. @@ -75,7 +133,28 @@ Switching between `#[builder(default)]` and `Option` is [compatible](./compat ::: -```rust +::: code-group + +```rust [Struct] +#[derive(bon::Builder)] +struct Example { + // This uses the `Default` trait // [!code highlight] + #[builder(default)] // [!code highlight] + a: u32, + + // This uses the given custom default value // [!code highlight] + #[builder(default = 4)] // [!code highlight] + b: u32, +} + +// Here, the default values will be used `a = 0` and `b = 4` // [!code highlight] +let result = Example::builder().build(); + +assert_eq!(result.a, 0); +assert_eq!(result.b, 4); +``` + +```rust [Function] #[bon::builder] fn example( // This uses the `Default` trait // [!code highlight] @@ -95,9 +174,48 @@ let result = example().call(); assert_eq!(result, 4); ``` +```rust [Method] +struct Example; + +#[bon::bon] +impl Example { + #[builder] + fn example( + // This uses the `Default` trait // [!code highlight] + #[builder(default)] // [!code highlight] + a: u32, + + // This uses the given custom default value // [!code highlight] + #[builder(default = 4)] // [!code highlight] + b: u32, + ) -> u32 { + a + b + } +} + +// Here, the default values will be used `a = 0` and `b = 4` // [!code highlight] +let result = Example::example().call(); + +assert_eq!(result, 4); +``` + +::: + The same [pair of optional setters](#setters-pair) is generated for members with default values. -```rust ignore +::: code-group + +```rust ignore [Struct] +let result = Example::builder() + // Pass a non-None value + .a(3) + // Pass an `Option` value directly. `None` means the default + // value will be used (4 in this case) + .maybe_b(None) + .build(); +``` + +```rust ignore [Function] let result = example() // Pass a non-None value .a(3) @@ -107,6 +225,18 @@ let result = example() .call(); ``` +```rust ignore [Method] +let result = Example::example() + // Pass a non-None value + .a(3) + // Pass an `Option` value directly. `None` means the default + // value will be used (4 in this case) + .maybe_b(None) + .call(); +``` + +::: + You can also reference other members in the default expression. See [`#[builder(default)]`](../../reference/builder/member/default#evaluation-context) reference for details. ## Conditional Building diff --git a/website/src/guide/basics/positional-members.md b/website/src/guide/basics/positional-members.md index 3b499e7f..c1ffaa94 100644 --- a/website/src/guide/basics/positional-members.md +++ b/website/src/guide/basics/positional-members.md @@ -2,14 +2,14 @@ You can let the caller pass some values as positional parameters to the starting function, that creates the builder or to the finishing function, that consumes it. -## Starting function +## Starting Function Use `#[builder(start_fn)]` to move some members to the parameters of the starting function. -```rust -use bon::Builder; +::: code-group -#[derive(Builder)] +```rust [Struct] +#[derive(bon::Builder)] // Top-level attribute to give a custom name for the starting function // [!code highlight] #[builder(start_fn = with_coordinates)] // [!code highlight] struct Treasure { @@ -33,6 +33,65 @@ assert_eq!(treasure.y, 9); assert_eq!(treasure.label.as_deref(), Some("oats")); ``` +```rust [Function] +// The starting function's name is the name of the underlying function itself, +// that's why we don't really need `#[builder(start_fn = ...)] rename here +// unlike in #[derive(Builder)] syntax case. +#[bon::builder] +fn display_treasure( + // Member-level attributes to move members // [!code highlight] + // to the parameters of `display_treasure()` // [!code highlight] + #[builder(start_fn)] // [!code highlight] + x: u32, + + #[builder(start_fn)] // [!code highlight] + y: u32, + + label: Option, +) -> String { + format!("{x}, {y}, {label:?}") +} + +let treasure = display_treasure(2, 9) // [!code highlight] + .label("oats".to_owned()) + .call(); + +assert_eq!(treasure, r#"2, 9, Some("oats")"#) +``` + +```rust [Method] +struct Example; + +#[bon::bon] +impl Example { + // The starting function's name is the name of the underlying function itself, + // that's why we don't really need `#[builder(start_fn = ...)] rename here + // unlike in #[derive(Builder)] syntax case. + #[builder] + fn display_treasure( + // Member-level attributes to move members // [!code highlight] + // to the parameters of `display_treasure()` // [!code highlight] + #[builder(start_fn)] // [!code highlight] + x: u32, + + #[builder(start_fn)] // [!code highlight] + y: u32, + + label: Option, + ) -> String { + format!("{x}, {y}, {label:?}") + } +} + +let treasure = Example::display_treasure(2, 9) // [!code highlight] + .label("oats".to_owned()) + .call(); + +assert_eq!(treasure, r#"2, 9, Some("oats")"#) +``` + +::: + ::: tip There are two versions of the `#[builder(start_fn)]` used here: [top-level](../../reference/builder/top-level/start_fn) and [member-level](../../reference/builder/member/start_fn). @@ -40,14 +99,14 @@ They have different meanings. ::: -## Finishing function +## Finishing Function Use `#[builder(finish_fn)]` to move some members to the parameters of the finishing function. -```rust -use bon::Builder; +::: code-group -#[derive(Builder)] +```rust [Struct] +#[derive(bon::Builder)] // Top-level attribute to give a custom name for the finishing function // [!code highlight] #[builder(finish_fn = located_at)] // [!code highlight] struct Treasure { @@ -71,6 +130,61 @@ assert_eq!(treasure.y, 9); assert_eq!(treasure.label.as_deref(), Some("oats")); ``` +```rust [Function] +// Top-level attribute to give a custom name for the finishing function // [!code highlight] +#[bon::builder(finish_fn = located_at)] // [!code highlight] +fn treasure( + // Member-level attributes to move members // [!code highlight] + // to the parameters of `located_at()` // [!code highlight] + #[builder(finish_fn)] // [!code highlight] + x: u32, + + #[builder(finish_fn)] // [!code highlight] + y: u32, + + label: Option, +) -> String { + format!("{x}, {y}, {label:?}") +} + +let treasure = treasure() + .label("oats".to_owned()) + .located_at(2, 9); // [!code highlight] + +assert_eq!(treasure, r#"2, 9, Some("oats")"#); +``` + +```rust [Method] +struct Example; + +#[bon::bon] +impl Example { + // Top-level attribute to give a custom name for the finishing function // [!code highlight] + #[builder(finish_fn = located_at)] // [!code highlight] + fn treasure( + // Member-level attributes to move members // [!code highlight] + // to the parameters of `located_at()` // [!code highlight] + #[builder(finish_fn)] // [!code highlight] + x: u32, + + #[builder(finish_fn)] // [!code highlight] + y: u32, + + label: Option, + ) -> String { + format!("{x}, {y}, {label:?}") + } +} + +let treasure = Example::treasure() + .label("oats".to_owned()) + .located_at(2, 9); // [!code highlight] + +assert_eq!(treasure, r#"2, 9, Some("oats")"#); +``` + +::: + ::: tip There are two versions of the `#[builder(finish_fn)]` used here: [top-level](../../reference/builder/top-level/finish_fn) and [member-level](../../reference/builder/member/finish_fn).