diff --git a/README.v3.md b/README.v3.md new file mode 100644 index 00000000..3071ffb0 --- /dev/null +++ b/README.v3.md @@ -0,0 +1,247 @@ + + bon home + + +
+ github + crates.io + docs.rs + docs.rs +
+ +
+ + + + + + + + + + + +
📖 Guide BookNarrative introduction
🔍 API ReferenceAttributes API index
+
+ + + + +# Overview + +`bon` is a Rust crate for generating compile-time-checked builders for structs and functions. It also provides idiomatic partial application with optional and named parameters for functions and methods. + +If you wonder "Why would I use builders?", see the [motivational blog post](https://bon-rs.com/blog/how-to-do-named-function-arguments-in-rust). + +## Function Builder + +You can turn a function with positional parameters into a function with named parameters with `#[builder]`. + +```rust +use bon::builder; + +#[builder] +fn greet(name: &str, level: Option) -> String { + let level = level.unwrap_or(0); + + format!("Hello {name}! Your level is {level}") +} + +let greeting = greet() + .name("Bon") + .level(24) // <- setting `level` is optional, we could omit it + .call(); + +assert_eq!(greeting, "Hello Bon! Your level is 24"); +``` + +Any syntax for functions is supported including `async`, fallible, generic functions, `impl Trait`, etc. + +Many things are customizable with additional attributes described in the [API reference](https://bon-rs.com/reference/builder), but let's see what else `bon` offers. + +## Struct Builder + +Use `#[derive(Builder)]` to generate a builder for a struct. + +```rust +use bon::Builder; + +#[derive(Builder)] +struct User { + name: String, + is_admin: bool, + level: Option, +} + +let user = User::builder() + .name("Bon".to_owned()) + // `level` is optional, we could omit it here + .level(24) + // call setters in any order + .is_admin(true) + .build(); + +assert_eq!(user.name, "Bon"); +assert_eq!(user.level, Some(24)); +assert!(user.is_admin); +``` + +## Method Builder + +Associated methods require `#[bon]` on top of the impl block additionally. + +### Method `new` + +The method named `new` generates `builder()/build()` methods. + +```rust +use bon::bon; + +struct User { + id: u32, + name: String, +} + +#[bon] +impl User { + #[builder] + fn new(id: u32, name: String) -> Self { + Self { id, name } + } +} + +let user = User::builder() + .id(1) + .name("Bon".to_owned()) + .build(); + +assert_eq!(user.id, 1); +assert_eq!(user.name, "Bon"); +``` + +`#[derive(Builder)]` on a struct generates builder API that is fully compatible with placing `#[builder]` on the `new()` method with a signature similar to the struct's fields (more details on the [Compatibility](https://bon-rs.com/guide/misc/compatibility#switching-between-derive-builder-and-builder-on-the-new-method) page). + +### Other Methods + +All other methods generate `{method_name}()/call()` methods. + +```rust +use bon::bon; + +struct Greeter { + name: String, +} + +#[bon] +impl Greeter { + #[builder] + fn greet(&self, target: &str, prefix: Option<&str>) -> String { + let prefix = prefix.unwrap_or("INFO"); + let name = &self.name; + + format!("[{prefix}] {name} says hello to {target}") + } +} + +let greeter = Greeter { name: "Bon".to_owned() }; + +let greeting = greeter + .greet() + .target("the world") + // `prefix` is optional, omitting it is fine + .call(); + +assert_eq!(greeting, "[INFO] Bon says hello to the world"); +``` + +Methods with or without `self` are both supported. + +## No Panics Possible + +Builders generated by `bon`'s macros use the typestate pattern to ensure all required parameters are filled, and the same setters aren't called repeatedly to prevent unintentional overwrites. + +If something is wrong, a compile error will be created. No matter how you use the generated builder a panic is never possible. + +| ⭐ Don't forget to give our repo a [star on Github ⭐](https://github.com/elastio/bon)! | +| --- | + +## What's Next? + +What you've seen above is the first page of the 📖 [Guide Book](https://bon-rs.com/guide/overview). Consider reading the `Basics` section. Begin [here](https://bon-rs.com/guide/basics/optional-members) with the optional/default values topic. Remember, knowledge is power 🐱! + +Feel free to jump to code and use the `#[builder]` and `#[derive(Builder)]` once you've seen enough docs to get started. + +The [🔍 API Reference](https://bon-rs.com/reference/builder) will help you navigate the attributes once you feel comfortable with the basics of `bon`. Both `#[derive(Builder)]` on structs and `#[builder]` on functions/methods have almost identical attributes API, so the documentation for them is common. + +## Installation + +Add `bon` to your `Cargo.toml`. + +```toml +[dependencies] +bon = "2.3" +``` + +You can opt out of `std` and `alloc` cargo features with `default-features = false` for `no_std` environments. + +## Getting Help + +If you can't figure something out, consult the docs and maybe use the `🔍 Search` bar on our [docs website](https://bon-rs.com). You may also create an issue or a discussion on the [Github repository](https://github.com/elastio/bon) for help or write us a message on [Discord](https://bon-rs.com/discord). + +## Socials + + + + + + + + + + + + + +
DiscordHere you can leave feedback, ask questions, report bugs, or just write "thank you".
X (Twitter)Profile of the maintainer. There are only posts about bon and Rust in general here.
+ +## Acknowledgments + +This project was heavily inspired by such awesome crates as [`buildstructor`](https://docs.rs/buildstructor), [`typed-builder`](https://docs.rs/typed-builder) and [`derive_builder`](https://docs.rs/derive_builder). This crate was designed with many lessons learned from them. + +See [alternatives](https://bon-rs.com/guide/misc/alternatives) for comparison. + +## License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + + diff --git a/bon-macros/src/builder/builder_gen/member/config/mod.rs b/bon-macros/src/builder/builder_gen/member/config/mod.rs index 033774da..ebe0455d 100644 --- a/bon-macros/src/builder/builder_gen/member/config/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/config/mod.rs @@ -20,7 +20,7 @@ pub(crate) struct MemberConfig { /// Assign a default value to the member it it's not specified. /// /// An optional expression can be provided to set the value for the member, - /// otherwise its [`Default`] trait impl will be used. + /// otherwise its [`Default`] trait impl will be used. #[darling(with = parse_optional_expr, map = Some)] pub(crate) default: Option>>, diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index db1b5b11..a90f263c 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -368,7 +368,7 @@ impl BuilderGenCtx { if you found yourself needing it, then you are probably doing something wrong; \ feel free to open an issue/discussion in our GitHub repository \ (https://github.com/elastio/bon) or ask for help in our Discord server \ - (https://discord.gg/QcBYSamw4c)"; + (https://bon-rs.com/discord)"; quote! { #[doc(hidden)] diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 170fbb35..e380d95a 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -233,8 +233,8 @@ impl<'a> StateModGenCtx<'a> { let docs = format!( "Marker trait that indicates that all required members are set.\n\n\ - In this state, you can finish the building by calling the method \ - [`{builder_ident}::{finish_fn}()`]", + In this state, you can finish building by calling the method \ + [`{builder_ident}::{finish_fn}()`](super::{builder_ident}::{finish_fn}())", ); quote! { diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index f869c4ab..5d5ab87d 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -153,7 +153,16 @@ pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { CompletionsSchema::leaf("finish_fn"), CompletionsSchema::leaf("state_mod"), CompletionsSchema::leaf("on").set_custom_filter(|meta| { - if !meta.is_empty() { + if let Some(first) = meta.first() { + if let Meta::Path(path) = first { + if path.is_ident("into") + || path.is_ident("transparent") + || path.is_ident("overwritable") + { + return; + } + } + meta.remove(0); } }), diff --git a/bon/Cargo.toml b/bon/Cargo.toml index 84a88077..71f42c6a 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -14,7 +14,7 @@ categories = [ keywords = ["builder", "macro", "derive", "constructor", "setter"] edition = "2021" -homepage = "https://bon-rs.com/" +homepage = "https://bon-rs.com" license = "MIT OR Apache-2.0" repository = "https://github.com/elastio/bon" diff --git a/bon/src/__/ide.rs b/bon/src/__/ide.rs index 8babac5e..59ddcb50 100644 --- a/bon/src/__/ide.rs +++ b/bon/src/__/ide.rs @@ -8,87 +8,93 @@ pub mod builder_top_level { use super::*; - /// See the docs at + /// See the docs at pub const builder_type: Option = None; pub mod builder_type { use super::*; - /// See the docs at + /// See the docs at pub const name: Identifier = Identifier; - /// See the docs at + /// See the docs at pub const vis: VisibilityString = VisibilityString; - /// See the docs at + /// See the docs at pub const doc: DocComments = DocComments; } - /// See the docs at + /// See the docs at pub const finish_fn: Option = None; - /// See the docs at + /// See the docs at pub mod finish_fn { use super::*; - /// See the docs at + /// See the docs at pub const name: Identifier = Identifier; - /// See the docs at + /// See the docs at pub const vis: VisibilityString = VisibilityString; - /// See the docs at + /// See the docs at pub const doc: DocComments = DocComments; } - /// See the docs at + /// See the docs at pub const start_fn: Option = None; - /// See the docs at + /// See the docs at pub mod start_fn { use super::*; - /// See the docs at + /// See the docs at pub const name: Identifier = Identifier; - /// See the docs at + /// See the docs at pub const vis: VisibilityString = VisibilityString; - /// See the docs at + /// See the docs at pub const doc: DocComments = DocComments; } - /// See the docs at + /// See the docs at pub const state_mod: Option = None; - /// See the docs at + /// See the docs at pub mod state_mod { use super::*; - /// See the docs at + /// See the docs at pub const name: Identifier = Identifier; - /// See the docs at + /// See the docs at pub const vis: VisibilityString = VisibilityString; - /// See the docs at + /// See the docs at pub const doc: DocComments = DocComments; } - /// See the docs at + /// See the docs at pub mod on { use super::*; - /// See the docs at + /// See the docs at pub const into: Flag = Flag; + + /// See the docs at + pub const transparent: Flag = Flag; + + /// See the docs at + pub const overwritable: Flag = Flag; } - /// See the docs at + /// See the docs at pub mod derive { - /// See the docs at + /// See the docs at pub use core::fmt::Debug; - /// See the docs at + /// See the docs at pub use core::clone::Clone; } @@ -96,7 +102,7 @@ pub mod builder_top_level { /// It's hinted with an underscore due to the limitations of the current /// completions limitation. This will be fixed in the future. /// - /// See the docs at + /// See the docs at pub const crate_: Option = None; } diff --git a/bon/src/examples/comprehensive.rs b/bon/src/examples/comprehensive.rs new file mode 100644 index 00000000..4333f590 --- /dev/null +++ b/bon/src/examples/comprehensive.rs @@ -0,0 +1,27 @@ +//! Comprehensive example of the generated builder and its typestate API. +//! +//! The preliminary reading of [Typestate API](https://bon-rs.com/guide/typestate-api) +//! guide is recommended to understand how the pieces in this example fit together. +//! +//! This module contains a struct [`Example`] that was annotated with [`#[derive(Builder)]`](crate::Builder). +//! The config [`#[builder(state_mod(vis = "pub"))]`](https://bon-rs.com/reference/builder/top-level/state_mod) +//! was applied to make the generated builder's typestate API public and visible here in the docs. +//! +//! The following was generated by the macro: +//! - [`ExampleBuilder`] - the builder struct itself +//! - [`example_builder`] - the builder's typestate API module + +/// Example struct with the `#[derive(Builder)]` annotation. +#[derive(crate::Builder)] +#[builder(crate = crate, state_mod(vis = "pub"))] +pub struct Example { + /// Example required member + x1: u32, + + /// Example optional member + x2: Option, + + /// Example member with a default value. + #[builder(default = 2 + 2)] + x3: u32, +} diff --git a/bon/src/examples/minimal.rs b/bon/src/examples/minimal.rs new file mode 100644 index 00000000..751c9fb9 --- /dev/null +++ b/bon/src/examples/minimal.rs @@ -0,0 +1,14 @@ +//! Minimal example of the generated builder and its typestate API. +//! +//! This documentation was generated as a showcase for the [Builder's Type Signature] +//! guide +//! +//! [Builder's Type Signature]: https://bon-rs.com/guide/typestate-api/builders-type-signature + +/// Example struct with the `#[derive(Builder)]` annotation. +#[derive(crate::Builder)] +#[builder(crate = crate, state_mod(vis = "pub"))] +pub struct Example { + x1: u32, + x2: u32, +} diff --git a/bon/src/examples/mod.rs b/bon/src/examples/mod.rs new file mode 100644 index 00000000..4192a67c --- /dev/null +++ b/bon/src/examples/mod.rs @@ -0,0 +1,2 @@ +pub mod comprehensive; +pub mod minimal; diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 36f6a57c..9076d325 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -22,7 +22,11 @@ mod collections; they should not be used directly; if you found a need for this, then you are probably \ doing something wrong; feel free to open an issue/discussion in our GitHub repository \ (https://github.com/elastio/bon) or ask for help in our Discord server \ - (https://discord.gg/QcBYSamw4c)"] + (https://bon-rs.com/discord)"] pub mod __; mod builder_state; + +/// Examples of generated builders' APIs. +#[cfg(doc)] +pub mod examples; diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index c6972e62..aeaa51c0 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -1,8 +1,25 @@ -import { defineConfig } from "vitepress"; +import { defineConfig, HeadConfig } from "vitepress"; import { abbr } from "@mdit/plugin-abbr"; import * as v1 from "../src/v1/config.mjs"; import * as v2 from "../src/v2/config.mjs"; +const head: HeadConfig[] = [ + ["link", { rel: "icon", href: `bon-logo-thumb.png` }], + ["meta", { property: "og:image", content: `bon-logo-thumb.png` }], +]; + +// Enable analytics only in the final build on CI. Locally, it's not needed. +if (process.env.CI) { + head.push([ + "script", + { + defer: "", + src: "https://umami.bon-rs.com/script.js", + "data-website-id": "10c1ad05-7a6e-49ee-8633-5f8f75de4ab9", + }, + ]); +} + // https://vitepress.dev/reference/site-config export default defineConfig({ title: "Bon", @@ -46,6 +63,7 @@ export default defineConfig({ markdown: { languageAlias: { + // Attributes highlighting works better with JS tokenizer 😳 attr: "js", }, @@ -62,25 +80,10 @@ export default defineConfig({ srcExclude: ["README.md", "infra/**"], - head: [ - ["link", { rel: "icon", href: `bon-logo-thumb.png` }], - ["meta", { property: "og:image", content: `bon-logo-thumb.png` }], - [ - "script", - { - defer: "", - src: "https://umami.bon-rs.com/script.js", - "data-website-id": "10c1ad05-7a6e-49ee-8633-5f8f75de4ab9", - }, - ], - ], + head, srcDir: "src", - rewrites: { - "guide/:subdir/:page": "guide/:page", - }, - // https://vitepress.dev/reference/default-theme-config themeConfig: { logo: "/bon-logo-thumb.png", @@ -98,9 +101,13 @@ export default defineConfig({ text: "Edit this page on GitHub", }, - search: { - provider: "local", - }, + // Enable the search only in the final build on CI. Locally, it takes additional + // time during the dev HMR server startup and config reloads. + search: !process.env.CI + ? undefined + : { + provider: "local", + }, nav: [ { text: "Guide", link: "/guide/overview" }, @@ -111,7 +118,7 @@ export default defineConfig({ socialLinks: [ { icon: "github", link: "https://github.com/elastio/bon" }, - { icon: "discord", link: "https://discord.gg/QcBYSamw4c" }, + { icon: "discord", link: "https://bon-rs.com/discord" }, { icon: "x", link: "https://x.com/veetaha" }, ], @@ -120,56 +127,70 @@ export default defineConfig({ ...v2.sidebars, "/guide": [ { - text: "Guide", + text: "Overview", + link: "/guide/overview", + }, + { + text: "Basics", items: [ { - text: "Overview", - link: "/guide/overview", + text: "Optional Members", + link: "/guide/basics/optional-members", }, { - text: "Optional Members", - link: "/guide/optional-members", + text: "Into Conversions", + link: "/guide/basics/into-conversions", }, { - text: "Customizing Setters", - link: "/guide/customizing-setters", + text: "Custom Conversions", + link: "/guide/basics/custom-conversions", }, { text: "Positional Members", - link: "/guide/positional-members", + link: "/guide/basics/positional-members", }, { text: "Inspecting", - link: "/guide/inspecting", + link: "/guide/basics/inspecting", }, { text: "Documenting", - link: "/guide/documenting", + link: "/guide/basics/documenting", }, + ], + }, + { + text: "Typestate API", + link: "/guide/typestate-api", + items: [ { - text: "Builder Extensions", - link: "/guide/builder-extensions", + text: "Builder's Type Signature", + link: "/guide/typestate-api/builders-type-signature", + }, + { + text: "Custom Methods", + link: "/guide/typestate-api/custom-methods", } - ] + ], }, { text: "Patterns", items: [ { text: "Conditional Building", - link: "/guide/conditional-building", + link: "/guide/patterns/conditional-building", }, { text: "Fallible Builders", - link: "/guide/fallible-builders", + link: "/guide/patterns/fallible-builders", }, { text: "Into Conversions In-Depth", - link: "/guide/into-conversions-in-depth", + link: "/guide/patterns/into-conversions-in-depth", }, { text: "Shared Configuration", - link: "/guide/shared-configuration", + link: "/guide/patterns/shared-configuration", }, ], }, @@ -178,23 +199,23 @@ export default defineConfig({ items: [ { text: "Compatibility", - link: "/guide/compatibility", + link: "/guide/misc/compatibility", }, { text: "Limitations", - link: "/guide/limitations", + link: "/guide/misc/limitations", }, { text: "Benchmarks", - link: "/guide/benchmarks", + link: "/guide/misc/benchmarks", }, { text: "Alternatives", - link: "/guide/alternatives", + link: "/guide/misc/alternatives", }, { text: "Troubleshooting", - link: "/guide/troubleshooting", + link: "/guide/misc/troubleshooting", }, ], }, @@ -203,7 +224,7 @@ export default defineConfig({ items: [ { text: "Contributing", - link: "/guide/contributing", + link: "/guide/internal/contributing", }, ], }, @@ -220,7 +241,7 @@ export default defineConfig({ items: [ { text: "builder_type", - link: "/reference/builder/top-level/builder-type", + link: "/reference/builder/top-level/builder_type", }, { text: "crate", @@ -232,7 +253,7 @@ export default defineConfig({ }, { text: "finish_fn", - link: "/reference/builder/top-level/finish-fn", + link: "/reference/builder/top-level/finish_fn", }, { text: "on", @@ -240,11 +261,11 @@ export default defineConfig({ }, { text: "start_fn", - link: "/reference/builder/top-level/start-fn", + link: "/reference/builder/top-level/start_fn", }, { text: "state_mod", - link: "/reference/builder/top-level/state-mod", + link: "/reference/builder/top-level/state_mod", }, ], }, @@ -259,7 +280,7 @@ export default defineConfig({ }, { text: "finish_fn", - link: "/reference/builder/member/finish-fn", + link: "/reference/builder/member/finish_fn", }, { text: "into", @@ -283,7 +304,7 @@ export default defineConfig({ }, { text: "start_fn", - link: "/reference/builder/member/start-fn", + link: "/reference/builder/member/start_fn", }, { text: "transparent", @@ -295,10 +316,6 @@ export default defineConfig({ }, ], }, - { - text: "Typestate API", - link: "/reference/builder/typestate-api", - }, ], }, { diff --git a/website/data/version.data.ts b/website/data/version.data.ts deleted file mode 100644 index 27afdbdf..00000000 --- a/website/data/version.data.ts +++ /dev/null @@ -1,11 +0,0 @@ -import toml from "smol-toml"; -import fs from "node:fs/promises"; - -export default { - watch: ['../../bon/Cargo.toml'], - async load([cargoTomlPath]: [string]) { - const cargoTomlContent = await fs.readFile(cargoTomlPath, "utf-8"); - const cargoToml = toml.parse(cargoTomlContent) as any; - return cargoToml.package.version; - } -} diff --git a/website/package-lock.json b/website/package-lock.json index 2d03cda3..cacbda17 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -18,10 +18,9 @@ "htmlparser2": "^9.1.0", "leven": "^4.0.0", "medium-zoom": "^1.1.0", - "smol-toml": "^1.3.0", "ts-node": "^10.9.2", "ts-pattern": "^5.5.0", - "vitepress": "^1.4.2" + "vitepress": "^1.4.5" } }, "node_modules/@algolia/autocomplete-core": { @@ -2671,18 +2670,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/smol-toml": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.0.tgz", - "integrity": "sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==", - "dev": true, - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3019,9 +3006,9 @@ } }, "node_modules/vitepress": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.4.2.tgz", - "integrity": "sha512-10v92Lqx0N4r7YC3cQLBvu+gRS2rHviE7vgdKiwlupUGfSWkyiQDqYccxM5iPStDGSi1Brnec1lf+lmhaQcZXw==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.4.5.tgz", + "integrity": "sha512-9K0k8kvdEbeowVCpKF/x0AySSq0Pr9pM8xufLgQcKMjsifwxtDjXJjcFhZv4LYw2dcpdYiBq2j7PnWi0tCaMCg==", "dev": true, "dependencies": { "@docsearch/css": "^3.6.2", diff --git a/website/package.json b/website/package.json index 5ac9560d..51a11a5f 100644 --- a/website/package.json +++ b/website/package.json @@ -17,10 +17,9 @@ "htmlparser2": "^9.1.0", "leven": "^4.0.0", "medium-zoom": "^1.1.0", - "smol-toml": "^1.3.0", "ts-node": "^10.9.2", "ts-pattern": "^5.5.0", - "vitepress": "^1.4.2" + "vitepress": "^1.4.5" }, "dependencies": { "vue": "^3.5.12" diff --git a/website/src/blog/bon-builder-generator-v2-release.md b/website/src/blog/bon-builder-generator-v2-release.md index d29e0632..d029403d 100644 --- a/website/src/blog/bon-builder-generator-v2-release.md +++ b/website/src/blog/bon-builder-generator-v2-release.md @@ -60,11 +60,11 @@ Now the documentation was split into the ["Guide"](../guide/overview) and ["Refe We added 3 new pages with guides on how to use builders idiomatically or solve some common problems (e.g. validating inputs): -- [Conditional Building](../guide/conditional-building) -- [Fallible Builders](../guide/fallible-builders) -- [Into Conversions In-Depth](../guide/into-conversions-in-depth) +- [Conditional Building](../guide/patterns/conditional-building) +- [Fallible Builders](../guide/patterns/fallible-builders) +- [Into Conversions In-Depth](../guide/patterns/into-conversions-in-depth) -I recommend you to check out the ["Into Conversions In-Depth"](../guide/into-conversions-in-depth) especially because it's highly related to one of the breaking changes that we'll review below. +I recommend you to check out the ["Into Conversions In-Depth"](../guide/patterns/into-conversions-in-depth) especially because it's highly related to one of the breaking changes that we'll review below. ## Breaking changes @@ -72,7 +72,7 @@ I recommend you to check out the ["Into Conversions In-Depth"](../guide/into-con This has been a topic of [controversy](https://github.com/elastio/bon/issues/15), but finally, we aligned on the decision to remove the magical automatic `Into` conversions. -The main reason for removing this is to make `bon` more obvious and intuitive. Rust's core pillar is "being explicit". By having automatic `Into` conversions `bon` v1 introduced magical implicit behaviour, that also could lead to some footguns. For a detailed explanation of the potential footguns, see the ["Into Conversions In-Depth"](../guide/into-conversions-in-depth) page. +The main reason for removing this is to make `bon` more obvious and intuitive. Rust's core pillar is "being explicit". By having automatic `Into` conversions `bon` v1 introduced magical implicit behaviour, that also could lead to some footguns. For a detailed explanation of the potential footguns, see the ["Into Conversions In-Depth"](../guide/patterns/into-conversions-in-depth) page. Now, if you want to enable `Into` conversions for a set of members, you can use the new [`#[builder(on(type_pattern, into))]`](../reference/builder/top-level/on) attribute. It allows you to specify the type that you want to enable `Into` conversions for explicitly. diff --git a/website/src/blog/bon-builder-v2-2-release.md b/website/src/blog/bon-builder-v2-2-release.md index fcd09799..1712fd92 100644 --- a/website/src/blog/bon-builder-v2-2-release.md +++ b/website/src/blog/bon-builder-v2-2-release.md @@ -61,9 +61,9 @@ cargo fmt A new attribute is now supported at the top level. You can add [`#[builder(derive(...))]`](../reference/builder/top-level/derive) to ask `bon` to generate implementations of `Clone` or `Debug` for the builder. -This helps with reusing [partial builders](../guide/conditional-building#shared-partial-builder), because now you can clone the builder where only part of the fields are set. +This helps with reusing [partial builders](../guide/patterns/conditional-building#shared-partial-builder), because now you can clone the builder where only part of the fields are set. -The `Debug` derive allows you to [inspect](../guide/inspecting) the builder state for debugging purposes. +The `Debug` derive allows you to [inspect](../guide/basics/inspecting) the builder state for debugging purposes. **Example:** @@ -222,7 +222,7 @@ If you like or dislike this change in syntax feel free to write a comment on Red ## Summary -We are listening to your feedback! If you'd like to propose a change in `bon`, or ask a question, or just say "thank you", consider joining our [newly launched Discord server](https://discord.gg/QcBYSamw4c)! +We are listening to your feedback! If you'd like to propose a change in `bon`, or ask a question, or just say "thank you", consider joining our [newly launched Discord server](https://bon-rs.com/discord)! Also, a huge thank you for 750 stars ⭐ [on Github](https://github.com/elastio/bon)! Consider giving [`bon`] a star if you haven't already. Your support and feedback are a big motivation and together we can build a better builder 🐱! diff --git a/website/src/blog/bon-builder-v2-3-release.md b/website/src/blog/bon-builder-v2-3-release.md index 9d8447dd..9a889513 100644 --- a/website/src/blog/bon-builder-v2-3-release.md +++ b/website/src/blog/bon-builder-v2-3-release.md @@ -22,7 +22,7 @@ If you don't know about [`bon`], then see the [motivational blog post](./how-to- ### Positional arguments in starting and finishing functions -While having the ability to use separate setters for the members gives you a ton of flexibility and extensibility described on the ["Compatibility"](../guide/compatibility) page, sometimes you don't need all of that. +While having the ability to use separate setters for the members gives you a ton of flexibility and extensibility described on the ["Compatibility"](../guide/misc/compatibility) page, sometimes you don't need all of that. Maybe you'd like to pick out some specific members and let the user pass their values as positional parameters to the starting function that creates the builder or to the finishing function that consumes it. This reduces the syntax a bit at the cost of some extensibility loss ⚖️, but it may be worth it! @@ -32,8 +32,8 @@ As an example, suppose we have a `Treasure` struct with `x` and `y` coordinates To do that we can use the `#[builder(start_fn)]` attribute. There are two contexts where we can place it, and they both have a different meaning: -- [Top-level `#[builder(start_fn = ...)]`](../reference/builder/top-level/start-fn) - configures the name of the starting function and optionally its visibility -- [Member-level `#[builder(start_fn)]`](../reference/builder/member/start-fn) - configures the member to be a positional parameter on the starting function +- [Top-level `#[builder(start_fn = ...)]`](../reference/builder/top-level/start_fn) - configures the name of the starting function and optionally its visibility +- [Member-level `#[builder(start_fn)]`](../reference/builder/member/start_fn) - configures the member to be a positional parameter on the starting function We'll want to use both of these attributes in our example to give a better name for the starting function that describes its inputs and configure `x` and `y` as positional parameters on the starting function as well. @@ -78,7 +78,7 @@ impl Treasure { Now let's say we need to know the person who claimed the `Treasure`. While describing the treasure using the current builder syntax we'd like the person who claimed it to specify their first name and last name at the end of the building process. -We can use a similar combination of the [top-level `#[builder(finish_fn = ...)]`](../reference/builder/top-level/finish-fn) and the [member-level `#[builder(finish_fn)]`](../reference/builder/member/finish-fn) attributes to do that. +We can use a similar combination of the [top-level `#[builder(finish_fn = ...)]`](../reference/builder/top-level/finish_fn) and the [member-level `#[builder(finish_fn)]`](../reference/builder/member/finish_fn) attributes to do that. **Example:** @@ -115,7 +115,7 @@ assert_eq!(treasure.claimed_by_first_name, "Lyra"); // [!code highlight] assert_eq!(treasure.claimed_by_last_name, "Heartstrings"); // [!code highlight] ``` -You may also combine these attributes with [`#[builder(into)]`](../reference/builder/member/into) or [`#[builder(on(..., into))]`](../reference/builder/top-level/on) to reduce the number of `to_owned()` calls a bit. See this described in detail on the new ["Positional members"](../guide/positional-members#into-conversions) page in the guide. +The new [Positional Members](../guide/basics/positional-members) page was added to the guide. ### Guaranteed MSRV is 1.59.0 now @@ -143,7 +143,7 @@ This isn't a breaking change, and the code that uses `#[bon::builder]` on a stru Huge thank you for 925 stars ⭐ [on Github](https://github.com/elastio/bon)! Consider giving [`bon`] a star if you haven't already. Your support and feedback are a big motivation and together we can build a better builder 🐱! -Bon's goal is to empower everyone to build beautiful APIs with great flexibility and extensibility. If you have any feedback or ideas for improvement consider joining [our Discord server](https://discord.gg/QcBYSamw4c) to discuss them, or just [open an issue on Github](https://github.com/elastio/bon/issues). +Bon's goal is to empower everyone to build beautiful APIs with great flexibility and extensibility. If you have any feedback or ideas for improvement consider joining [our Discord server](https://bon-rs.com/discord) to discuss them, or just [open an issue on Github](https://github.com/elastio/bon/issues). ::: tip diff --git a/website/src/blog/how-to-do-named-function-arguments-in-rust.md b/website/src/blog/how-to-do-named-function-arguments-in-rust.md index a9a597dc..0a02afa0 100644 --- a/website/src/blog/how-to-do-named-function-arguments-in-rust.md +++ b/website/src/blog/how-to-do-named-function-arguments-in-rust.md @@ -146,7 +146,7 @@ User::builder() .build(); ``` -So, you can use just one builder crate solution consistently for everything. Builders for functions and structs both share the same API design, which allows you, for example, to switch between a `#[derive(Builder)]` on a struct and a `#[builder]` attribute on a method that creates a struct. This won't be an API-breaking change for your consumers ([details](../guide/compatibility#switching-between-derive-builder-and-builder-on-the-new-method)). +So, you can use just one builder crate solution consistently for everything. Builders for functions and structs both share the same API design, which allows you, for example, to switch between a `#[derive(Builder)]` on a struct and a `#[builder]` attribute on a method that creates a struct. This won't be an API-breaking change for your consumers ([details](../guide/misc/compatibility#switching-between-derive-builder-and-builder-on-the-new-method)). ## Summary diff --git a/website/src/changelog.md b/website/src/changelog.md index ddc0e7a0..d15651e6 100644 --- a/website/src/changelog.md +++ b/website/src/changelog.md @@ -49,7 +49,7 @@ All the breaking changes are very unlikely to actually break your code that was - ⚠️ **Breaking.** Builder macros now generate additional `mod builder_name {}` where `builder_name` is the snake_case version of the name of the builder struct. This new module contains the type state API of the builder. There is a low probability that this new module name may conflict with existing symbols in your scope, so this change is marked as breaking. -- Add [`#[builder(builder_type(vis = "...", doc { ... }))]`](https://bon-rs.com/reference/builder/top-level/builder-type) that allows overriding the visibility and docs of the builder struct ([#145](https://github.com/elastio/bon/pull/145)) +- Add [`#[builder(builder_type(vis = "...", doc { ... }))]`](https://bon-rs.com/reference/builder/top-level/builder_type) that allows overriding the visibility and docs of the builder struct ([#145](https://github.com/elastio/bon/pull/145)) - Add [`#[builder(finish_fn(vis = "...", doc { ... } ))]`](https://bon-rs.com/reference/builder/top-level/finish-fn) that allows overriding the visibility and docs of the finishing function ([#145](https://github.com/elastio/bon/pull/145)) diff --git a/website/src/guide/basics/custom-conversions.md b/website/src/guide/basics/custom-conversions.md new file mode 100644 index 00000000..68e0e8b9 --- /dev/null +++ b/website/src/guide/basics/custom-conversions.md @@ -0,0 +1,31 @@ +# Custom Conversions + +[`#[builder(into)]`](../../reference/builder/member/into) is great and it works in many cases. However, what if you need to do a conversion that isn't a simple `Into`? What if you want your setter to accept several parameters? What if your setter should be fallible? The answer to all these questions is the bigger brother [`#[builder(with)]`](../../reference/builder/member/with). + +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; + +struct Point { + x: u32, + y: u32, +} + +#[derive(Builder)] +struct Example { + #[builder(with = |x: u32, y: u32| Point { x, y })] + point: Point, +} + +let value = Example::builder() + .point(2, 3) + .build(); + +assert_eq!(value.point.x, 2); +assert_eq!(value.point.y, 3); +``` + +You can also pass a [fallible closure](../../reference/builder/member/with#fallible-setters) and some [well-known functions](../../reference/builder/member/with#well-known-functions) to `#[builder(with)]`. + +If your setter needs more complex logic that isn't expressible with `#[builder(with)]` (e.g. mark the setter `unsafe`, set several members at once), then [Custom Methods](../typestate-api/custom-methods) can cover that. diff --git a/website/src/guide/basics/documenting.md b/website/src/guide/basics/documenting.md new file mode 100644 index 00000000..6413a496 --- /dev/null +++ b/website/src/guide/basics/documenting.md @@ -0,0 +1,70 @@ +# Documenting + +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] +fn greet( + /// Name of the person to greet. + /// + /// **Example:** + /// ``` + /// greet().name("John"); + /// ``` + name: &str, + + /// Age expressed in full years passed since the birth date. + age: u32 +) -> String { + format!("Hello {name} with age {age}!") +} +``` + +::: details How does this work? 🤔 + +This works because Rust compiler checks for invalid placement of `#[doc = ...]` attributes only after the macro expansion stage. `#[builder]` removes the docs from the function's arguments in the expanded code, and instead moves them to the docs on setter methods. + +::: + +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 + +You can override documentation on other items generated by builder macros. There are multiple attributes accepting a `doc { ... }` block. + +```rust +use bon::Builder; + +#[derive(Builder)] +#[builder( + builder_type(doc { + /// Custom docs on the builder struct itself + }), + start_fn(doc { + /// Custom docs on the starting function + }), + // ... +)] +struct Example {} +``` + +You can document the following items this way: + +| Attribute | Documentation target +|------------------------------------------------------------------------|---------------------- +| [`builder_type`](../../reference/builder/top-level/builder_type#doc) | Builder struct +| [`start_fn`](../../reference/builder/top-level/start_fn#doc) | Starting function +| [`finish_fn`](../../reference/builder/top-level/finish_fn#doc) | Finishing function +| [`state_mod`](../../reference/builder/top-level/state_mod#doc) | Builder state API module (more details in [Typestate API](../typestate-api)) +| [`setters`](../../reference/builder/member/setters#doc) | Custom docs for setters. Prevents copying them from the field/argument + + + +## 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/inspecting.md b/website/src/guide/basics/inspecting.md similarity index 79% rename from website/src/guide/inspecting.md rename to website/src/guide/basics/inspecting.md index 1872c70b..938a93ec 100644 --- a/website/src/guide/inspecting.md +++ b/website/src/guide/basics/inspecting.md @@ -1,6 +1,6 @@ # Inspecting -If you want to inspect the values set in the builder for debugging purposes you can leverage the [`#[builder(derive(...))]`](../reference/builder/top-level/derive) attribute to derive the [`Debug`](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html) trait for your builder. +If you want to inspect the values set in the builder for debugging purposes you can leverage the [`#[builder(derive(...))]`](../../reference/builder/top-level/derive) attribute to derive the [`Debug`](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html) trait for your builder. **Example:** @@ -30,4 +30,4 @@ assert_eq!( builder.is_admin(true).call(); ``` -You can also derive the [`Clone`](https://doc.rust-lang.org/stable/std/clone/trait.Clone.html) trait for your builder using this same attribute. See more details in the [reference for the `#[builder(derive(...))]` attribute](../reference/builder/top-level/derive). +You can also derive the [`Clone`](https://doc.rust-lang.org/stable/std/clone/trait.Clone.html) trait 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/into-conversions.md b/website/src/guide/basics/into-conversions.md new file mode 100644 index 00000000..e56330af --- /dev/null +++ b/website/src/guide/basics/into-conversions.md @@ -0,0 +1,50 @@ +# `Into` conversions + +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; +use std::path::PathBuf; + +#[derive(Builder)] // [!code focus] +struct Project { // [!code focus] + name: String, // [!code focus] + description: String, // [!code focus] + path: PathBuf, // [!code focus] +} // [!code focus] + +Project::builder() + .name("Bon".to_owned()) // [!code focus] + .description("Awesome crate 🐱".to_string()) // [!code focus] + .path("/path/to/bon".into()) // [!code focus] + .build(); +``` + +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; +use std::path::PathBuf; + +// All setters for members of type `String` will accept `impl Into` // [!code highlight] +#[derive(Builder)] // [!code highlight] +#[builder(on(String, into))] // [!code highlight] +struct Project { + name: String, + description: String, + + // The setter only for this member will accept `impl Into` // [!code highlight] + #[builder(into)] // [!code highlight] + path: PathBuf, +} + +Project::builder() + .name("Bon") // [!code highlight] + .description("Awesome crate 🐱") // [!code highlight] + .path("/path/to/your/heart") // [!code highlight] + .build(); +``` + +`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/optional-members.md b/website/src/guide/basics/optional-members.md similarity index 62% rename from website/src/guide/optional-members.md rename to website/src/guide/basics/optional-members.md index 1294e2c4..2ba1556a 100644 --- a/website/src/guide/optional-members.md +++ b/website/src/guide/basics/optional-members.md @@ -6,7 +6,7 @@ outline: deep ## `Option` -Setters generated for members of `Option` type are optional to call. If they aren't invoked, then `None` will be used as the default. +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 #[bon::builder] @@ -16,7 +16,7 @@ fn example(level: Option) {} example().call(); ``` -You can use [`#[builder(transparent)]`](../reference/builder/member/transparent) to opt-out from this. +You can use [`#[builder(transparent)]`](../../reference/builder/member/transparent) to opt-out from this. ### Setters pair @@ -27,10 +27,10 @@ The builder provides a **pair** of setters for each optional member: | `{member}` | `T` | Accepts a non-`None` value. | [`some_fn`][setters] | `maybe_{member}` | `Option` | Accepts an `Option` directly. | [`option_fn`][setters] -[setters]: ../reference/builder/member/setters +[setters]: ../../reference/builder/member/setters -::: details See how the setters look like in the generated code +::: details See how the setters look in the generated code ```rust ignore // [GENERATED CODE (simplified)] @@ -45,7 +45,7 @@ impl ExampleBuilder { ::: -Thanks to this design, changing the member from required to optional [preserves compatibility](./compatibility#making-a-required-member-optional). +Thanks to this design, changing the member from required to optional [preserves compatibility](../misc/compatibility#making-a-required-member-optional). ### Examples @@ -69,16 +69,14 @@ example().maybe_level(value).call(); ## `#[builder(default)]` -To make a member of non-`Option` type optional you may use the attribute [`#[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. +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. ::: tip -Switching between `#[builder(default)]` and `Option` is [compatible](./compatibility#switching-between-option-t-and-builder-default). +Switching between `#[builder(default)]` and `Option` is [compatible](../misc/compatibility#switching-between-option-t-and-builder-default). ::: -### Examples - ```rust #[bon::builder] fn example( @@ -105,11 +103,14 @@ The same [pair of optional setters](#setters-pair) is generated for members with let result = example() // Pass a non-None value .a(3) - // Pass an `Option` value directly - .maybe_b(Some(5)) + // 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 -Now that you know how optional members work you can check out the ["Conditional building" design patterns](./conditional-building) or continue studying other features of `bon` by following the "Next page" link at the bottom. +Now that you know how optional members work you can check out the [Conditional building](../patterns/conditional-building) design patterns or continue studying other features of `bon` by following the "Next page" link at the bottom. diff --git a/website/src/guide/basics/positional-members.md b/website/src/guide/basics/positional-members.md new file mode 100644 index 00000000..a44741a6 --- /dev/null +++ b/website/src/guide/basics/positional-members.md @@ -0,0 +1,79 @@ +# Positional Members + +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 + +Use `#[builder(start_fn)]` to move some members to the parameters of the starting function. + +```rust +use bon::Builder; + +#[derive(Builder)] +// Top-level attribute to give a better name for the starting function // [!code highlight] +#[builder(start_fn = with_coordinates)] // [!code highlight] +struct Treasure { + // Member-level attributes to move members // [!code highlight] + // to the parameters of `with_coordinates()` // [!code highlight] + #[builder(start_fn)] // [!code highlight] + x: u32, + + #[builder(start_fn)] // [!code highlight] + y: u32, + + label: Option, +} + +let treasure = Treasure::with_coordinates(2, 9) // [!code highlight] + .label("oats".to_owned()) + .build(); + +assert_eq!(treasure.x, 2); +assert_eq!(treasure.y, 9); +assert_eq!(treasure.label.as_deref(), 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). +They have different meanings. + +::: + +## Finishing function + +Use `#[builder(finish_fn)]` to move some members to the parameters of the finishing function. + +```rust +use bon::Builder; + +#[derive(Builder)] +// Top-level attribute to give a better name for the finishing function // [!code highlight] +#[builder(finish_fn = located_at)] // [!code highlight] +struct 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, +} + +let treasure = Treasure::builder() + .label("oats".to_owned()) + .located_at(2, 9); // [!code highlight] + +assert_eq!(treasure.x, 2); +assert_eq!(treasure.y, 9); +assert_eq!(treasure.label.as_deref(), 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). +They have different meanings. + +::: diff --git a/website/src/guide/builder-extensions.md b/website/src/guide/builder-extensions.md deleted file mode 100644 index 253c9cb7..00000000 --- a/website/src/guide/builder-extensions.md +++ /dev/null @@ -1,4 +0,0 @@ -# Builder Extensions - - -## Custom setters diff --git a/website/src/guide/customizing-setters.md b/website/src/guide/customizing-setters.md deleted file mode 100644 index ef8086d2..00000000 --- a/website/src/guide/customizing-setters.md +++ /dev/null @@ -1 +0,0 @@ -# Customizing Setters diff --git a/website/src/guide/documenting.md b/website/src/guide/documenting.md deleted file mode 100644 index aec8207e..00000000 --- a/website/src/guide/documenting.md +++ /dev/null @@ -1,38 +0,0 @@ -# Documenting - -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] -fn greet( - /// Name of the person to greet. - /// - /// **Example:** - /// ``` - /// greet().name("John"); - /// ``` - name: &str, - - /// Age expressed in full years passed since the birth date. - age: u32 -) -> String { - format!("Hello {name} with age {age}!") -} -``` - -::: details How does this work? 🤔 - -This works because Rust compiler checks for invalid placement of `#[doc = ...]` attributes only after the macro expansion stage. `#[builder]` makes sure to remove the docs from the function's arguments in the expanded code, and instead moves them to the docs on setter methods. - -::: - -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. - -## 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/misc/alternatives.md b/website/src/guide/misc/alternatives.md index a48e6df1..44f1ba13 100644 --- a/website/src/guide/misc/alternatives.md +++ b/website/src/guide/misc/alternatives.md @@ -53,7 +53,7 @@ fn main() { ::: tip -Why is there an explicit `main()` function in this code snippet 🤔? It's a long story explained in a [blog post](../blog/the-weird-of-function-local-types-in-rust) (feel free to skip). +Why is there an explicit `main()` function in this code snippet 🤔? It's a long story explained in a [blog post](/blog/the-weird-of-function-local-types-in-rust) (feel free to skip). ::: @@ -98,8 +98,8 @@ Another difference is that fields of collection types are considered required by [map]: https://docs.rs/bon/latest/bon/macro.map.html [set]: https://docs.rs/bon/latest/bon/macro.set.html [mutators]: https://docs.rs/typed-builder/latest/typed_builder/derive.TypedBuilder.html#mutators -[bon-on]: ../reference/builder/top-level/on -[bon-into]: ../reference/builder/member/into +[bon-on]: ../../reference/builder/top-level/on +[bon-into]: ../../reference/builder/member/into [bs-into]: https://docs.rs/buildstructor/latest/buildstructor/#into-field [db-into]: https://docs.rs/derive_builder/latest/derive_builder/#generic-setters [r1]: #special-setter-methods-for-collections diff --git a/website/src/guide/misc/compatibility.md b/website/src/guide/misc/compatibility.md index 6f47b26a..5b23793c 100644 --- a/website/src/guide/misc/compatibility.md +++ b/website/src/guide/misc/compatibility.md @@ -2,7 +2,7 @@ ## Making a required member optional -It's totally backwards compatible to make a required member optional by changing the type from `T` to `Option` or by adding [`#[builder(default)]`](../reference/builder/member/default) to it. +It's totally backwards compatible to make a required member optional by changing the type from `T` to `Option` or by adding [`#[builder(default)]`](../../reference/builder/member/default) to it. This is because both required and optional members have a setter that accepts `T` (not wrapped in an `Option`). The only change to the public API when making the required member optional is that a `maybe_`-prefixed setter is added to the builder. That new method accepts an `Option`. @@ -179,4 +179,4 @@ let user = User::builder() Let's suppose you have existing code that defines functions with positional parameters in the public API. You'd like to change it to expose builder syntax instead, but you want to keep the old code compatible with the positional functions API. -In this case, you may use the top-level attribute `#[builder(start_fn)]` to keep both syntaxes available. See examples in the [docs for this attribute](../reference/builder/top-level/start-fn#exposing-original-function). +In this case, you may use the top-level attribute `#[builder(start_fn)]` to keep both syntaxes available. See examples in the [docs for this attribute](../../reference/builder/top-level/start_fn#exposing-original-function). diff --git a/website/src/guide/overview.md b/website/src/guide/overview.md index 8cde4882..796583ec 100644 --- a/website/src/guide/overview.md +++ b/website/src/guide/overview.md @@ -1,313 +1 @@ - - -# Overview - -`bon` is a Rust crate for generating compile-time-checked builders for functions and structs. It also provides idiomatic partial application with optional and named parameters for functions and methods. - -If you are new to the concept of builders or named function arguments, and you don't know what problems they may solve for you, then check out the motivational [blog post](../blog/how-to-do-named-function-arguments-in-rust). - -## Installation - -Add this to your `Cargo.toml` to use this crate: - -```toml-vue -[dependencies] -bon = "{{ versionWildcard }}" -``` - -You can opt out of `std` and `alloc` cargo features with `default-features = false` for `no_std` environments. - - -## Builder for a function - -`bon` can turn a function with positional parameters into a function with "named" parameters via a builder. It's as easy as placing the `#[builder]` macro on top of it. - -**Example:** - -```rust -use bon::builder; - -#[builder] // [!code highlight] -fn greet(name: &str, age: u32) -> String { - format!("Hello {name} with age {age}!") -} - -let greeting = greet() - .name("Bon") - .age(24) - .call(); - -assert_eq!(greeting, "Hello Bon with age 24!"); -``` - -::: tip - -Many things are customizable with additional attributes. [`#[builder]` macro reference](../reference/builder) describes all of them. - -::: - -Any syntax for functions is supported including `async`, fallible, generic functions, `impl Trait`, etc. If you find an edge case where `bon` doesn't work, please [create an issue on GitHub](https://github.com/elastio/bon/issues). - -## Builder for an associated method - -You can also generate a builder for associated methods. For this to work you need to add a `#[bon]` macro on top of the `impl` block additionally. - -**Example:** - -```rust -use bon::bon; - -struct Counter { - val: u32, -} - -#[bon] // <- this macro is required on the impl block // [!code highlight] -impl Counter { - #[builder] // [!code highlight] - fn new(initial: Option) -> Self { - Self { - val: initial.unwrap_or_default(), - } - } - - #[builder] // [!code highlight] - fn increment(&mut self, diff: u32) { - self.val += diff; - } -} - -let mut counter = Counter::builder() - .initial(3) - .build(); - -counter - .increment() - .diff(3) - .call(); - -assert_eq!(counter.val, 6); -``` - -::: details Why is that `#[bon]` macro on top of the `impl` block required? 🤔 (feel free to skip) - -There are a couple of technical reasons. - -First, it's the lack of surrounding context given to a proc macro in Rust. A proc macro sees only the syntax it is placed on top of. For example, the `#[builder]` macro inside of the `impl` block can't see the `impl Counter` part of the impl block above it. However, it needs that information to tell the actual type of `Self`. - -Second, the `#[builder]` proc macro generates new items such as the builder struct type definition, which it needs to output **adjacently** to the `impl` block itself. However, proc macros in Rust can only modify the part of the syntax they are placed on and generate new items on the same level of nesting. The `#[builder]` macro inside of the `impl` block can't just break out of it. - -::: - -::: details Why does it compile without an import of `bon::builder`? 🤔 (feel free to skip) - -This is because there is no separate `#[builder]` proc macro running in this case. Only the `#[bon]` macro handles code generation, it's an active attribute, while `#[builder]` is a dumb inert data attribute (see [the Rust Reference](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes) for details about active and inert attributes). - -It wouldn't harm if `bon::builder` was imported. It won't shadow the inert `#[builder]` attribute, but the compiler will report that the import of that macro is unused. - -::: - - -To follow the usual Rust builder naming conventions `bon` treats the method named `new` inside of the impl block specially. It generates functions with a bit of different names. - -If `#[builder]` is placed on the method called `new`, then the generated functions are called: - -| Starting function | Finishing function -|---------------------------|--------------------- -| `builder() -> {T}Builder` | `build(self) -> T` - -For any other methods not called `new` and for any free function the naming is a bit different: - -| Starting function | Finishing function -|------------------------------------------------|--------------------- -| `{fn_name}() -> {T?}{PascalCaseFnName}Builder` | `call(self) -> T` - -## Builder for a struct - -`bon` supports the classic pattern of annotating a struct to generate a builder with the `#[derive(Builder)]` syntax. - -**Example:** - -```rust -use bon::Builder; - -#[derive(Builder)] -struct User { - id: u32, - name: String, -} - -let user = User::builder() - .id(1) - .name("Bon".to_owned()) - .build(); - -assert_eq!(user.id, 1); -assert_eq!(user.name, "Bon"); -``` - -::: tip - -`#[derive(Builder)]` on a struct generates builder API that is fully compatible with placing `#[builder]` attribute on the `new()` method with a signature similar to the struct's fields. - -See [compatibility](./compatibility#switching-between-derive-builder-and-builder-on-the-new-method) page for details. -::: - -In general, both `#[derive(Builder)]` on structs and `#[builder]` on functions/methods have almost the same API. We'll use both of them throughout the documentation to provide examples. If the example shows only the usage of one syntax (e.g. `#[builder]`), it's very likely that the other syntax (e.g. `#[derive(Builder)]`) works similarly unless explicitly stated otherwise. - -## No panics possible - -The builders generated by `#[builder]` and `#[derive(Builder)]` use the typestate pattern to make sure all required parameters are filled, and the same setters aren't called repeatedly to prevent unintentional overwrites and typos. If something is wrong, a compile error will be created. There are no potential panics and `unwrap()`s involved. - -## `Option` values are optional - -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`. - -It also generates two setters: one accepts `T` and the other accepts `Option`. The first avoids wrapping values with `Some()` on the call site. The second allows passing the `Option` value directly. - -```rust -use bon::Builder; - -#[derive(Builder)] -struct Projection { - x: Option, - y: Option, - - // Use an annotation for members of non-`Option` type - #[builder(default)] - z: u32, -} - -// Both `x` and `y` will be set to `None`, `z` will be set to `0` -Projection::builder().build(); - -Projection::builder() - // Pass the value without wrapping it with `Some()` - .x(10) - // Or use a `maybe_`-prefixed setter that accepts `Option` - .maybe_y(Some(20)) - // The APIs generated for `#[builder(default)]` and `Option` are equivalent. - // `z` will be set to `0` when `build()` is called. - .maybe_z(None) - .build(); -``` - -See [optional members](./optional-members) page for details. - -## `Into` conversions - -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()`. - -**Example:** - -```rust -use bon::Builder; -use std::path::PathBuf; - -#[derive(Builder)] // [!code focus] -struct Project { // [!code focus] - name: String, // [!code focus] - description: String, // [!code focus] - path: PathBuf, // [!code focus] -} // [!code focus] - -Project::builder() - .name("Bon".to_owned()) // [!code focus] - .description("Awesome crate 🐱".to_string()) // [!code focus] - .path("/path/to/bon".into()) // [!code focus] - .build(); -``` - -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; -use std::path::PathBuf; - -// All setters for members of type `String` will accept `impl Into` // [!code highlight] -#[derive(Builder)] // [!code highlight] -#[builder(on(String, into))] // [!code highlight] -struct Project { - name: String, - description: String, - - // The setter only for this member will accept `impl Into` // [!code highlight] - #[builder(into)] // [!code highlight] - path: PathBuf, -} - -Project::builder() - // &str is converted to `String` internally // [!code highlight] - .name("Bon") - .description("Awesome crate 🐱") - // `&str` is converted to `PathBuf` internally // [!code highlight] - .path("/path/to/your/heart") - .build(); -``` - -See the ["Into Conversions In-Depth"](./into-conversions-in-depth) page for more details and important caveats (!). - -## What's next? - -::: tip - -If you like the idea of this crate and want to say "thank you" or "keep up doing this" consider giving us a [star ⭐ on Github](https://github.com/elastio/bon). Any support and contribution are appreciated 🐱! - -::: - -This is just part of what's available in `bon`. You may consider reading the rest of the `Guide` section to harness the full power of `bon` and understand the decisions it makes. - -You can also consult the [API reference index](../reference/builder) that describes all available configuration attributes. This guide will cover most of them but not all. Check the short descriptions of available attributes to see if something might be of immediate interest to you. - -However, feel free to skip the docs and just use the `#[builder]` and `#[derive(Builder)]` in your code. They are designed to be intuitive, so they'll probably do the thing you want them to do already. - -If you can't figure something out, consult the docs and maybe use that search `🔍 Search` thing at the top to navigate. You may also create an issue or a discussion on the [Github repository](https://github.com/elastio/bon) for help or write us a message on [Discord](https://discord.gg/QcBYSamw4c) (see below). - -Click the "Next page" button at the bottom to proceed with the guide. - -## Socials - - - - - - - - - - - - -
-
- - Discord -
-
Here you can leave feedback, ask questions, report bugs, or just write "thank you".
-
- - X (Twitter) -
-
Profile of the maintainer. There are only posts about bon and Rust in general here.
- - -## Acknowledgments - -This project was heavily inspired by such awesome crates as [`buildstructor`](https://docs.rs/buildstructor), [`typed-builder`](https://docs.rs/typed-builder) and [`derive_builder`](https://docs.rs/derive_builder). This crate was designed with many lessons learned from them. - -See [alternatives](./alternatives) for comparison. + diff --git a/website/src/guide/patterns/conditional-building.md b/website/src/guide/patterns/conditional-building.md index bc22bb81..7373f4ad 100644 --- a/website/src/guide/patterns/conditional-building.md +++ b/website/src/guide/patterns/conditional-building.md @@ -1,6 +1,8 @@ # Conditional Building -On this page, we'll review a case when you have multiple branches in your code that need to set different values for different builder members. Since builders generated by `bon` use the type-state pattern and setters consume `self`, it is a bit more complicated for conditional code to use them. But, not until you know the patterns described below 🐱. So let's learn how to write better conditional code 📚. +On this page, we'll review a case when you have multiple branches in your code that need to set different values for different builder members. + +Since builders generated by `bon` use the typestate pattern and setters consume `self`, it is a bit more complicated for conditional code to use them. But, not until you know the patterns described below 🐱. So let's learn how to write better conditional code 📚. The patterns described here aren't mutually exclusive. You can combine them as you see necessary to keep your code clean. @@ -56,7 +58,7 @@ Once the `build()` method is called, we no longer have the context of how exactl ## Shared total builder -In contrast to the [shared partial builder](#shared-partial-builder), here we are going to use the builder strictly _after_ the conditional code. The conditional code needs to create the variables that hold the component values for the builder beforehand. +In contrast to the [shared partial builder](#shared-partial-builder), here we'll use the builder strictly _after_ the conditional code. The conditional code needs to create the variables that hold the component values for the builder beforehand. **Example:** @@ -102,11 +104,11 @@ let user = User::builder() .build(); ``` -In this case, we create a variable for each conditional member beforehand and initialize them separately, then we just pass the results to the builder. We benefit from the `maybe_` setters for [optional members](./optional-members) such that we can pass the `Option` values directly. +In this case, we create a variable for each conditional member beforehand and initialize them separately, then we pass the results to the builder. We benefit from the `maybe_` setters for [optional members](../basics/optional-members) such that we can pass the `Option` values directly. ::: tip NOTE -However, creating separate variables is not strictly required. You can inline the usage of the variables in such a simple code like here, where each branch of the `if` takes a single line. Anyhow, branches can be much bigger in real code. +Creating separate variables is not strictly required. You can inline the usages of variables in such simple code as here, where each branch of the `if` takes a single line. Anyhow, branches can be much bigger in real code. ::: diff --git a/website/src/guide/patterns/into-conversions-in-depth.md b/website/src/guide/patterns/into-conversions-in-depth.md index f5168e14..003b73f7 100644 --- a/website/src/guide/patterns/into-conversions-in-depth.md +++ b/website/src/guide/patterns/into-conversions-in-depth.md @@ -7,7 +7,7 @@ outline: deep ## Preface -This is the continuation of the ["Into conversions" section](./overview#into-conversions) from the general overview page. This page describes the important caveats of using `impl Into` that you should know before enabling them. +This is the continuation of [Into Conversions](../basics/into-conversions) from the `Basics` section. This page describes important caveats of using `impl Into` that you should know before enabling them. Make sure you are familiar with the standard [`From`](https://doc.rust-lang.org/stable/std/convert/trait.From.html) and [`Into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html) traits before you proceed. Reading their docs is pretty much enough. For example, you should know that every type that implements `From` automatically implements `Into`. Also, you should know that you can pass a value of type `T` at no cost directly to a function that accepts `impl Into` thanks to [this blanket](https://github.com/rust-lang/rust/blob/1a94d839be8b248b972b9e022cb940d56de72fa1/library/core/src/convert/mod.rs#L763-L771) impl in `std`. diff --git a/website/src/guide/patterns/shared-configuration.md b/website/src/guide/patterns/shared-configuration.md index c169d47a..cd59b58b 100644 --- a/website/src/guide/patterns/shared-configuration.md +++ b/website/src/guide/patterns/shared-configuration.md @@ -8,7 +8,9 @@ On this page, you'll learn how to share common configurations for builders to av ## 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: +As an example, let's suppose you want to enable [`Into` conversions](../patterns/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`. + +You'll quickly run into a problem, where you need to repeat the same configuration for every usage of the builder macro. ```rust use bon::Builder; @@ -33,25 +35,23 @@ struct MyLovelyStruct2 { /**/ } ::: tip -This code uses the [`#[builder(on(...))]`](../reference/builder/top-level/on) attribute to configure the types of members for which `bon` should enable `Into` conversions. +This code uses the [`#[builder(on(...))]`](../../reference/builder/top-level/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 you 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. -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. +Use this approach if you have a lot of structs/functions in your crate that need a builder. -So with the [`macro_rules_attribute`] your code will look like this: +### Structs ```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. +// Under the hood, it creates a macro with `pub(crate)` visibility. attribute_alias! { #[apply(derive_builder!)] = #[derive(::bon::Builder)] @@ -69,13 +69,8 @@ struct MyLovelyStruct1 { /**/ } 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}; @@ -100,6 +95,6 @@ fn my_lovely_fn2(/**/) { /**/ } 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. +There is a proposed solution to this problem in the issue [#144](https://github.com/elastio/bon/issues/144). Add a 👍 to that issue if your use case needs a solution for this. It would be even better if you left a comment describing 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/ diff --git a/website/src/guide/positional-members.md b/website/src/guide/positional-members.md deleted file mode 100644 index 18847842..00000000 --- a/website/src/guide/positional-members.md +++ /dev/null @@ -1,146 +0,0 @@ -# Positional Members - -While having the ability to use separate setters for the members gives you a ton of flexibility and extensibility described on the ["Compatibility"](./compatibility) page, sometimes you don't need all of that. - -Maybe you'd like to pick out some specific members and let the user pass their values as positional parameters to the starting function that creates the builder or to the finishing function that consumes it. This reduces the syntax a bit at the cost of some extensibility loss ⚖️, but it may be worth it! - -## Starting function - -As an example, suppose we have a `Treasure` struct with `x` and `y` coordinates and a `label` that describes the payload of the treasure. Since all treasures are located somewhere, they all have coordinates, and it would be cool to specify them in a single starting function call. - -To do that we can use the `#[builder(start_fn)]` attribute. There are two contexts where we can place it, and they both have a different meaning: - -- [Top-level `#[builder(start_fn = ...)]`](../reference/builder/top-level/start-fn) - configures name, visibility and docs of the starting function -- [Member-level `#[builder(start_fn)]`](../reference/builder/member/start-fn) - configures the member to be a positional parameter on the starting function - -We'll want to use both of these attributes in our example to give a better name for the starting function that describes its inputs and configure `x` and `y` as positional parameters on the starting function as well. - -**Example:** - -```rust -use bon::Builder; - -#[derive(Builder)] -// Top-level attribute to give a better name for the starting function // [!code highlight] -#[builder(start_fn = with_coordinates)] // [!code highlight] -struct Treasure { - // Member-level attributes to make members as // [!code highlight] - // parameters of `with_coordinates()` // [!code highlight] - #[builder(start_fn)] // [!code highlight] - x: u32, - - #[builder(start_fn)] // [!code highlight] - y: u32, - - label: Option, -} - -let treasure = Treasure::with_coordinates(2, 9) // [!code highlight] - .label("oats".to_owned()) - .build(); - -assert_eq!(treasure.x, 2); -assert_eq!(treasure.y, 9); -assert_eq!(treasure.label.as_deref(), Some("oats")); -``` - -Here, the generated `with_coordinates` method has the following signature: - -```rust ignore -impl Treasure { - fn with_coordinates(x: u32, y: u32) -> TreasureBuilder { /**/ } -} -``` - -## Finishing function - -Now let's say we need to know the person who claimed the `Treasure`. While describing the treasure using the current builder syntax we'd like the person who claimed it to specify their first name and last name at the end of the building process. - -We can use a similar combination of the [top-level `#[builder(finish_fn = ...)]`](../reference/builder/top-level/finish-fn) and the [member-level `#[builder(finish_fn)]`](../reference/builder/member/finish-fn) attributes to do that. - -**Example:** - -```rust -use bon::Builder; - -#[derive(Builder)] -#[builder( - start_fn = with_coordinates, - finish_fn = claim // [!code highlight] -)] -struct Treasure { - #[builder(start_fn)] - x: u32, - - #[builder(start_fn)] - y: u32, - - #[builder(finish_fn)] // [!code highlight] - claimed_by_first_name: String, // [!code highlight] - - #[builder(finish_fn)] // [!code highlight] - claimed_by_last_name: String, // [!code highlight] - - label: Option, -} - -let treasure = Treasure::with_coordinates(2, 9) - .label("oats".to_owned()) - .claim("Lyra".to_owned(), "Heartstrings".to_owned()); // [!code highlight] - -assert_eq!(treasure.x, 2); -assert_eq!(treasure.y, 9); -assert_eq!(treasure.label.as_deref(), Some("oats")); -assert_eq!(treasure.claimed_by_first_name, "Lyra"); // [!code highlight] -assert_eq!(treasure.claimed_by_last_name, "Heartstrings"); // [!code highlight] -``` - -## Into conversions - -You may also combine these attributes with [`#[builder(into)]`](../reference/builder/member/into) or [`#[builder(on(..., into))]`](../reference/builder/top-level/on) to reduce the number of `to_owned()` calls a bit. - -```rust -use bon::Builder; - -#[derive(Builder)] -#[builder( - start_fn = with_coordinates, - finish_fn = claim // [!code focus] -)] -struct Treasure { - #[builder(start_fn)] - x: u32, - - #[builder(start_fn)] - y: u32, - - #[builder(finish_fn, into)] // [!code focus] - claimed_by_first_name: String, // [!code focus] - - #[builder(finish_fn, into)] // [!code focus] - claimed_by_last_name: String, // [!code focus] - - #[builder(into)] // [!code focus] - label: Option, // [!code focus] -} - -let treasure = Treasure::with_coordinates(2, 9) - .label("oats") // [!code focus] - .claim("Lyra", "Heartstrings"); // [!code focus] -``` - -However, keep in mind that positional members (ones annotated with `#[builder(start_fn/finish_fn)]`) are always required to pass. There is no special treatment of the `Option` type for such members. - -For example `#[builder(into)]` on a regular (named) member of the `Option` type generates two setters: -- One that accepts `impl Into`. -- The other that accepts `Option>`. - -For positional members, the story is completely different because there are no separate setters generated for them. There is just a single starting or finishing function. So if you enable an into conversion for a positional member of the `Option` type, it will be accepted as `impl Into>` in the starting or finishing function. - -Also, the type pattern of the `#[builder(on(..., into))]` attribute matches the `Option` fully. So, for example `on(String, into)` will not match the positional member of type `Option`, but `on(Option, into)` will. - -::: tip - -In general, it's not recommended to annotate optional members with `#[builder(start_fn/finish_fn)]` because you can't omit setting them using the positional function call syntax. - -::: diff --git a/website/src/guide/typestate-api.md b/website/src/guide/typestate-api.md new file mode 100644 index 00000000..8dc2dfc1 --- /dev/null +++ b/website/src/guide/typestate-api.md @@ -0,0 +1,7 @@ +# Typestate API + +This section teaches you the builder's typestate API. It describes the underlying component traits and types of the builder type and how to use them. + +Reading this is optional. The typestate API is private by default. The users of your builder can't denote its type unless you enable [`#[builder(state_mod(vis = "pub"))]`](../reference/builder/top-level/state_mod). + +It is more of an advanced concept that you'll rarely need. However, "advanced" doesn't necessarily mean "complicated" in this case. So feel free to study this section if you feel like it. diff --git a/website/src/guide/typestate-api/builders-type-signature.md b/website/src/guide/typestate-api/builders-type-signature.md new file mode 100644 index 00000000..db40d891 --- /dev/null +++ b/website/src/guide/typestate-api/builders-type-signature.md @@ -0,0 +1,111 @@ +# Builder's Type Signature + +On this page, you'll learn how to spell the exact builder's type 📝. + +## Builder's Type Name + +The builder's type name is derived from the underlying item from which it was generated by default. + + + +It can also be overridden with [`#[builder(builder_type = NewName)]`](../../reference/builder/top-level/builder_type). + +## Generic Typestate Parameter + +Builders generated by `bon` macros use the typestate pattern. The builder's typestate doesn't depend on the syntax from which it was generated (struct or function). + +Every time you call a setter the builder's type changes. The builder always contains a generic parameter `S` (stands for "state") at the end. This parameter holds the type state that describes what members were set in the builder. + +It's probably easier to understand with an example. Don't worry, the next paragraph will explain everything. + +```rust +use bon::Builder; + +#[derive(Builder)] +struct Example { + x1: u32, + x2: u32, +} + +// Import type states from the generated module +use example_builder::{SetX1, SetX2}; + +let builder: ExampleBuilder = Example::builder(); +let builder: ExampleBuilder = builder.x1(1); +let builder: ExampleBuilder> = builder.x2(2); +``` + +Notice the pattern here. Every time we set a member, we wrap the previous type state with a new `Set{Member}` type state transition. + +There is a special `Empty` type state, which is used as the default value for the generic parameter `S` in two places: +- The builder type itself: `ExampleBuilder` +- The type state transitions: `Set{Member}` + +The type states come from the builder's state module. The name of that module is the `snake_case` version of the builder's name. + +## Visibility + +The type state module is private by default and only accessible within the module where the builder macro was used. Thus, the builder type becomes unnameable outside of the module where it was generated. + +If you want to expose your builder's type signature, you need to add [`#[builder(state_mod(vis = "..."))]`](../../reference/builder/top-level/state_mod), where `...` can be `pub` or `pub(crate)` or any other visibility you want to expose the state module under. + +## Example Rustdoc + +You can see the `rustdoc` API reference generated for this example [here](https://docs.rs/bon/latest/bon/examples/minimal/). Note that it was generated with `#[builder(state_mod(vis = "pub"))]`, otherwise, it wouldn't appear in public documentation. + +## Other Generic Parameters + +The builder inherits all generic parameters from the struct or function from which it was generated. + +Functions may even use anonymous lifetimes and `impl Trait` syntax. Every such anonymous lifetime or `impl Trait` will get a separate generic parameter generated in the builder's type automatically. + +::: code-group + +```rust [Struct] +use bon::Builder; + +#[derive(Builder)] +struct Example<'a, T> { + x1: &'a T, +} + +let builder: ExampleBuilder<'_, bool, _> = Example::builder().x1(&true); + // ^- type state (always last) +``` + +```rust [Function] +use bon::builder; + +#[builder] +fn example( + x1: &impl Clone +) {} + +let builder: ExampleBuilder<'_, bool, _> = example().x1(&true); + // ^- type state (always last) +``` + +```rust [Method] +use bon::bon; + +struct Example; + +#[bon] +impl Example { + #[builder] + fn method(x1: &impl Clone) {} +} + +let builder: ExampleMethodBuilder<'_, bool, _> = Example::method().x1(&true); + // ^- type state (always last) +``` + +::: + +If there is a mix of named and anonymous lifetimes or named generic types and `impl Trait`, then the generated generic lifetimes and types will be appended at the end of the list of named lifetimes and other named generic parameters respectively. + +## What's Next? + +Now you know the mechanics of how a builder's type is built, so you can denote it when it's returned from a function or stored in a struct. + +However, to be able to write useful custom methods on the builder, you'll need to know the `trait`s behind the type states. Go to the next page to learn more. diff --git a/website/src/guide/typestate-api/custom-methods.md b/website/src/guide/typestate-api/custom-methods.md new file mode 100644 index 00000000..15b8acc6 --- /dev/null +++ b/website/src/guide/typestate-api/custom-methods.md @@ -0,0 +1,4 @@ +# Custom Methods + + +- TODO: add custom methods to the builder type 💪 diff --git a/website/src/reference/builder.md b/website/src/reference/builder.md index 36e85b52..48a5b1b6 100644 --- a/website/src/reference/builder.md +++ b/website/src/reference/builder.md @@ -12,12 +12,12 @@ These attributes are placed on top of a `struct` or `fn` declaration. | Attribute | Short description | -- | -- | -| [`builder_type`](./builder/top-level/builder-type) | Overrides name, visibility and docs for the builder struct +| [`builder_type`](./builder/top-level/builder_type) | Overrides name, visibility and docs for the builder struct | [`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 +| [`finish_fn`](./builder/top-level/finish_fn) | Overrides name, visibility and docs for the finishing function | [`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 +| [`start_fn`](./builder/top-level/start_fn) | Overrides name, visibility and docs for the starting function ## Member Attributes @@ -26,14 +26,14 @@ These attributes are placed on a `struct` field or `fn` argument. | Attribute | Short description | -- | -- | | [`default`](./builder/member/default) | Makes the member optional with a default value -| [`finish_fn`](./builder/member/finish-fn) | Makes the member a positional argument on the finishing function +| [`finish_fn`](./builder/member/finish_fn) | Makes the member a positional argument on the finishing function | [`into`](./builder/member/into) | Changes the signature of the setters to accept `impl Into` | [`name`](./builder/member/name) | Overrides the name of the member used in the builder's API -| [`overwritable` 🔬](./builder/member/overwritable) | Lets calling setters for the same member repeatedly +| [`overwritable` 🔬](./builder/member/overwritable) | Allows calling setters for the same member repeatedly | [`setters`](./builder/member/setters) | Overrides name, visibility and docs for setters | [`skip`](./builder/member/skip) | Skips generating setters for the member -| [`start_fn`](./builder/member/start-fn) | Makes the member a positional argument on the starting function -| [`transparent`](./builder/member/transparent) | ??????? TODO: ADD DOCS ?????? +| [`start_fn`](./builder/member/start_fn) | Makes the member a positional argument on the starting function +| [`transparent`](./builder/member/transparent) | Disables `Option` special handling, makes the member required | [`with`](./builder/member/with) | ??????? TODO: ADD DOCS ?????? ## Examples @@ -51,7 +51,7 @@ struct Example { } ``` -```rust [Free function] +```rust [Function] use bon::builder; #[builder(finish_fn = finish)] // <-- this is a top-level attribute // [!code highlight] @@ -61,7 +61,7 @@ fn example( ) { } ``` -```rust [Associated method] +```rust [Method] use bon::bon; struct Example; diff --git a/website/src/reference/builder/member/default.md b/website/src/reference/builder/member/default.md index ffd1c7e0..a7d256e1 100644 --- a/website/src/reference/builder/member/default.md +++ b/website/src/reference/builder/member/default.md @@ -1,175 +1,209 @@ # `default` -**Applies to:** +**Applies to:** -Makes the member optional and assigns a default value to it. There will be two setter methods generated for the member just like for [members of type `Option`](../../../guide/optional-members). One setter accepts a value of type `T` (type of the member) and the other (with the `maybe_` prefix) accepts an `Option`. +Makes the member optional and assigns a default value to it. The default value is lazily computed inside of the finishing function based on the form of this attribute. -::: tip +## Syntax -Switching between `#[builder(default)]` and `Option` is [compatible](../../../guide/compatibility#switching-between-option-t-and-builder-default). +| Form | How default value is computed +| -----------------------------------------|------------------------------- +| `#[builder(default)]` | `Default::default()` +| `#[builder(default = expression)]` | `expression` -::: +If combined with [`#[builder(into)]`](./into), the default expression is additionally converted via [`Into::into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html). + +## Setters -The default value will be lazily computed inside of the [finishing function](./finish-fn) (i.e. `build()` or `call()`). It is computed only if the setter for the member wasn't called or `None` was passed to the `maybe_{member}()` setter. +Two setter methods are generated for the member with `#[builder(default)]` just like for [members of type `Option`](../../../guide/basics/optional-members#setters-pair): -The default value is computed based on the form of this attribute: +| Name | Input | Description | Configuration attribute +|------------------|-------------|-------------------------------|------------------ +| `{member}` | `T` | Accepts a non-`None` value. | [`some_fn`](./setters) +| `maybe_{member}` | `Option` | Accepts an `Option` directly. | [`option_fn`](./setters) -| Form | How default value is computed | -| ---------------------------------- | ----------------------------- | -| `#[builder(default)]` | `Default::default()` | -| `#[builder(default = expression)]` | `expression` | +If `None` is passed to the `maybe_{member}` setter, then the default value is used. -The result of the `expression` will be converted into the target type using [`Into::into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html) if [`#[builder(into)]`](./into) is enabled for the setter. +::: tip + +Switching between `#[builder(default)]` and `Option` is [compatible](../../../guide/misc/compatibility#switching-between-option-t-and-builder-default). + +::: -**Example:** ::: code-group -```rust [Struct field] +```rust [Struct] use bon::Builder; #[derive(Builder)] -struct User { +struct Example { #[builder(default)] // [!code highlight] - level: u32, + x1: u32, - // The expression of type `&'static str` is automatically // [!code highlight] - // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight] - #[builder(into, default = "anon")] // [!code highlight] - name: String, + #[builder(default = "anon".to_owned())] // [!code highlight] + x2: String, - // Any complex expression is accepted // [!code highlight] - #[builder(default = bon::vec!["read"])] // [!code highlight] - permissions: Vec, + // No need for `.to_owned()`. Into is applied to the expression + #[builder(default = "bon", into)] // [!code highlight] + x3: String, } -let user = User::builder().build(); +let value = Example::builder().build(); + +assert_eq!(value.x1, 0); +assert_eq!(value.x2, "anon"); +assert_eq!(value.x3, "bon"); + +let value = Example::builder() + .x1(99) + .maybe_x2(None) // None means the default will be used + .maybe_x3(Some("lyra")) + .build(); -assert_eq!(user.level, 0); -assert_eq!(user.name, "anon"); -assert_eq!(user.permissions, ["read"]); +assert_eq!(value.x1, 99); +assert_eq!(value.x2, "anon"); +assert_eq!(value.x3, "lyra"); ``` -```rust [Free function argument] +```rust [Function] use bon::builder; #[builder] -fn greet_user( +fn example( #[builder(default)] // [!code highlight] - level: u32, - - // The expression of type `&'static str` is automatically // [!code highlight] - // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight] - #[builder(into, default = "anon")] // [!code highlight] - name: String, - - // Any complex expression is accepted // [!code highlight] - #[builder(default = bon::vec!["read"])] // [!code highlight] - permissions: Vec, -) -> String { - format!("Hello {name}! Your level is {level}, permissions: {permissions:?}") + x1: u32, + + #[builder(default = "anon".to_owned())] // [!code highlight] + x2: String, + + // No need for `.to_owned()`. Into is applied to the expression + #[builder(default = "bon", into)] // [!code highlight] + x3: String, +) -> (u32, String, String) { + (x1, x2, x3) } -let greeting = greet_user().call(); +let value = example().call(); + +assert_eq!(value.0, 0); +assert_eq!(value.1, "anon"); +assert_eq!(value.2, "bon"); + +let value = example() + .x1(99) + .maybe_x2(None) // None means the default will be used + .maybe_x3(Some("lyra")) + .call(); -assert_eq!(greeting, "Hello anon! Your level is 0, permissions: [\"read\"]"); +assert_eq!(value.0, 99); +assert_eq!(value.1, "anon"); +assert_eq!(value.2, "lyra"); ``` -```rust [Associated method argument] +```rust [Method] use bon::bon; -struct User { - level: u32, - name: String, - permissions: Vec, +struct Example { + x1: u32, + x2: String, + x3: String, } #[bon] -impl User { +impl Example { #[builder] fn new( #[builder(default)] // [!code highlight] - level: u32, + x1: u32, - // The expression of type `&'static str` is automatically // [!code highlight] - // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight] - #[builder(into, default = "anon")] // [!code highlight] - name: String, + #[builder(default = "anon".to_owned())] // [!code highlight] + x2: String, - // Any complex expression is accepted // [!code highlight] - #[builder(default = bon::vec!["read"])] // [!code highlight] - permissions: Vec, + // No need for `.to_owned()`. Into is applied to the expression + #[builder(default = "bon", into)] // [!code highlight] + x3: String, ) -> Self { - Self { level, name, permissions } + Self { x1, x2, x3 } } } -let user = User::builder().build(); +let value = Example::builder().build(); + +assert_eq!(value.x1, 0); +assert_eq!(value.x2, "anon"); +assert_eq!(value.x3, "bon"); + +let value = Example::builder() + .x1(99) + .maybe_x2(None) // None means the default will be used + .maybe_x3(Some("lyra")) + .build(); -assert_eq!(user.name, "anon"); -assert_eq!(user.level, 0); -assert_eq!(user.permissions, ["read"]); +assert_eq!(value.x1, 99); +assert_eq!(value.x2, "anon"); +assert_eq!(value.x3, "lyra"); ``` ::: -You can also use the values of other members by referencing their names in the `default` expression. All members are initialized in the order of their declaration. It means only those members that are declared earlier (higher) in the code are available to the `default` expression. +## Evaluation context -**Example:** +You can use the values of other members by referencing their names in the `default` expression. All members are initialized in the order of their declaration. It means only those members that are declared earlier (higher) in the code are available to the `default` expression. ::: code-group -```rust [Struct field] +```rust [Struct] use bon::Builder; #[derive(Builder)] struct Example { - member_1: u32, + x1: u32, - // Note that here we don't have access to `member_3` + // Note that here we don't have access to `x3` // because it's declared (and thus initialized) later - #[builder(default = 2 * member_1)] - member_2: u32, + #[builder(default = 2 * x1)] + x2: u32, - #[builder(default = member_2 + member_1)] - member_3: u32, + #[builder(default = x2 + x1)] + x3: u32, } -let example = Example::builder() - .member_1(3) +let value = Example::builder() + .x1(3) .build(); -assert_eq!(example.member_1, 3); -assert_eq!(example.member_2, 6); -assert_eq!(example.member_3, 9); +assert_eq!(value.x1, 3); +assert_eq!(value.x2, 6); +assert_eq!(value.x3, 9); ``` -```rust [Free function argument] +```rust [Function] use bon::builder; #[builder] fn example( - member_1: u32, + x1: u32, - // Note that here we don't have access to `member_3` + // Note that here we don't have access to `x3` // because it's declared (and thus initialized) later - #[builder(default = 2 * member_1)] - member_2: u32, + #[builder(default = 2 * x1)] + x2: u32, - #[builder(default = member_2 + member_1)] - member_3: u32, + #[builder(default = x2 + x1)] + x3: u32, ) -> (u32, u32, u32) { - (member_1, member_2, member_3) + (x1, x2, x3) } -let example = example() - .member_1(3) +let value = example() + .x1(3) .call(); -assert_eq!(example, (3, 6, 9)); +assert_eq!(value, (3, 6, 9)); ``` -```rust [Associated method argument] +```rust [Method] use bon::bon; struct Example; @@ -178,33 +212,33 @@ struct Example; impl Example { #[builder] fn example( - member_1: u32, + x1: u32, - // Note that here we don't have access to `member_3` + // Note that here we don't have access to `x3` // because it's declared (and thus initialized) later - #[builder(default = 2 * member_1)] - member_2: u32, + #[builder(default = 2 * x1)] + x2: u32, - #[builder(default = member_2 + member_1)] - member_3: u32, + #[builder(default = x2 + x1)] + x3: u32, ) -> (u32, u32, u32) { - (member_1, member_2, member_3) + (x1, x2, x3) } } -let example = Example::example() - .member_1(3) +let value = Example::example() + .x1(3) .call(); -assert_eq!(example, (3, 6, 9)); +assert_eq!(value, (3, 6, 9)); ``` ::: -## Caveats +### Caveats -The `self` parameter in associated methods is not available to the `default` expression. If you need the `self` context for your defaulting logic, then set your member's type to `Option` and handle the defaulting in the function's body manually. +The `self` parameter in associated method syntax is not available to the `default` expression. If you need the `self` context for your defaulting logic, then set your member's type to `Option` and handle the defaulting in the function's body manually. ## Compile errors -This attribute is incompatible with members of `Option` type, since `Option` already implies the default value of `None`. +This attribute is incompatible with members of `Option` type, since `Option` already implies the default value of `None`. However, it can be used together with [`#[builder(transparent)]`](./transparent). diff --git a/website/src/reference/builder/member/finish-fn.md b/website/src/reference/builder/member/finish-fn.md deleted file mode 100644 index e572f37e..00000000 --- a/website/src/reference/builder/member/finish-fn.md +++ /dev/null @@ -1,109 +0,0 @@ - -# `finish_fn` - -**Applies to:** - -Makes the member a positional argument on the finishing function that consumes the builder and returns the resulting object (for struct syntax) or performs the requested action (for function/method syntax). - -The ordering of members annotated with `#[builder(finish_fn)]` matters! They will appear in the same order relative to each other in the finishing function signature. They must also be declared at the top of the members list strictly after members annotated with [`#[builder(start_fn)]`](./start-fn) (if any). - -This ensures a consistent initialization order, and it makes these members available for expressions in `#[builder(default/skip = ...)]` for regular members that follow them. - -::: tip - -Don't confuse this with the top-level [`#[builder(finish_fn = ...)]`](../top-level/finish-fn) attribute that can be used to configure the name, visibility and docs of the finishing function. You'll likely want to use it in combination with this member-level attribute to define a better name for the finishing function. - -::: - -**Example:** - -::: code-group - -```rust [Struct field] -use bon::Builder; - -#[derive(Builder)] -// Top-level attribute to give a better name for the finishing function // [!code highlight] -#[builder(finish_fn = sign)] // [!code highlight] -struct Message { - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - author_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - author_last_name: String, - - payload: String, -} - -let message = Message::builder() - .payload("Bon is great! Give it a ⭐".to_owned()) - .sign("Sweetie".to_owned(), "Drops".to_owned()); - -assert_eq!(message.payload, "Bon is great! Give it a ⭐"); -assert_eq!(message.author_first_name, "Sweetie"); -assert_eq!(message.author_last_name, "Drops"); -``` - -```rust [Free function] -use bon::builder; - -// Top-level attribute to give a better name for the finishing function // [!code highlight] -#[builder(finish_fn = send)] // [!code highlight] -fn message( - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - receiver_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - receiver_last_name: String, - - payload: String, -) {} - -message() - .payload("Bon is great! Give it a ⭐".to_owned()) - .send("Sweetie".to_owned(), "Drops".to_owned()); -``` - -```rust [Associated method] -use bon::bon; - -struct Chat {} - -#[bon] -impl Chat { - // Top-level attribute to give a better name for the finishing function // [!code highlight] - #[builder(finish_fn = send)] // [!code highlight] - fn message( - &self, - - // Member-level attribute to mark the member as a parameter of `sign()` // [!code highlight] - #[builder(finish_fn)] // [!code highlight] - receiver_first_name: String, - - #[builder(finish_fn)] // [!code highlight] - receiver_last_name: String, - - payload: String, - ) {} -} - -let chat = Chat {}; - -chat.message() - .payload("Bon is great! Give it a ⭐".to_owned()) - .send("Sweetie".to_owned(), "Drops".to_owned()); -``` - -::: - -You can also combine this attribute with [`#[builder(into)]`](./into) or [`#[builder(on(..., into))]`](../top-level/on) to add an into conversion for the parameter. - -Importantly, `Into` conversions for such members work slightly differently from the regular (named) members in regard to the `Option` type. The `Option` type gives no additional meaning to the member annotated with `#[builder(finish_fn)]`. Thus, it is matched by the type pattern of `on(..., into)` and wrapped with `impl Into>` as any other type. - -::: tip - -In general, it's not recommended to annotate optional members with `#[builder(finish_fn)]` because you can't omit setting them using the positional function call syntax. - -::: diff --git a/website/src/reference/builder/member/finish_fn.md b/website/src/reference/builder/member/finish_fn.md new file mode 100644 index 00000000..bec247dc --- /dev/null +++ b/website/src/reference/builder/member/finish_fn.md @@ -0,0 +1,113 @@ +# `finish_fn` + +**Applies to:** + +Makes the member a positional argument on the finishing function. + +::: tip + +Don't confuse this with the [top-level](../top-level/finish_fn) `#[builder(finish_fn)]` attribute. + +::: + +::: code-group + +```rust [Struct] +use bon::Builder; + +#[derive(Builder)] +struct Example { + #[builder(finish_fn)] // [!code highlight] + x1: u32, + + #[builder(finish_fn)] // [!code highlight] + x2: u32, + + x3: u32, +} + +let value = Example::builder() + .x3(3) + .build(1, 2); // [!code highlight] + +assert_eq!(value.x1, 1); +assert_eq!(value.x2, 2); +assert_eq!(value.x3, 3); +``` + +```rust [Function] +use bon::builder; + +#[builder] +fn example( + #[builder(finish_fn)] // [!code highlight] + x1: u32, + + #[builder(finish_fn)] // [!code highlight] + x2: u32, + + x3: u32, +) -> (u32, u32, u32) { + (x1, x2, x3) +} + +let value = example() + .x3(3) + .call(1, 2); // [!code highlight] + +assert_eq!(value.0, 1); +assert_eq!(value.1, 2); +assert_eq!(value.2, 3); +``` + +```rust [Method] +use bon::bon; + +struct Example; + +#[bon] +impl Example { + #[builder] + fn example( + #[builder(finish_fn)] // [!code highlight] + x1: u32, + + #[builder(finish_fn)] // [!code highlight] + x2: u32, + + x3: u32, + ) -> (u32, u32, u32) { + (x1, x2, x3) + } +} + +let value = Example::example() + .x3(3) + .call(1, 2); // [!code highlight] + +assert_eq!(value.0, 1); +assert_eq!(value.1, 2); +assert_eq!(value.2, 3); +``` + +::: + +You can rename the finishing function from default `build()` or `call()` to something more readable via the top-level [`#[builder(finish_fn = ...)]`](../top-level/finish_fn) attribute. + +## Ordering + +The ordering of members annotated with `#[builder(finish_fn)]` matters! They will appear in the same order relative to each other in the finishing function signature. They must also be declared at the top of the members list strictly after members annotated with [`#[builder(start_fn)]`](./start_fn) (if any). + +It ensures a consistent initialization order, making these members available in the evaluation context of expressions in `#[builder(default/skip = ...)]` for regular members that follow them. + +## `Into` Conversions + +You can combine this attribute with [`#[builder(into)]`](./into) or [`#[builder(on(..., into))]`](../top-level/on) to add an `Into` conversion for the parameter. + +Importantly, `Into` conversions for such members work slightly differently from the regular (named) members regarding the `Option` type. There is no special handling of `Option` type for the members annotated with `#[builder(finish_fn)]`. Thus, they are matched by the type pattern of `on(..., into)` as any other type. + +::: tip + +In general, it's not recommended to annotate members of `Option` type with `#[builder(finish_fn)]` because you can't omit setting them using the positional function call syntax. + +::: diff --git a/website/src/reference/builder/member/into.md b/website/src/reference/builder/member/into.md index 0097a381..56ac8beb 100644 --- a/website/src/reference/builder/member/into.md +++ b/website/src/reference/builder/member/into.md @@ -1,7 +1,6 @@ - # `into` -**Applies to:** +**Applies to:** ::: tip @@ -11,19 +10,19 @@ This attribute is also configurable via the top-level [`#[builder(on(...))]`](.. Changes the signature of the setters to accept [`impl Into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html), where `T` is the type of the member. -For [optional members](../../../guide/optional-members), the `maybe_{member}()` setter method will accept an `Option>` type instead of just `Option`. +For [optional members](../../../guide/basics/optional-members), the `maybe_{member}()` setter method will accept an `Option>` type instead of just `Option`. For members that use `#[builder(default = expression)]`, the `expression` will be converted with `Into::into`. This parameter is often used with the `String` type, which allows you to pass `&str` into the setter without calling `.to_owned()` or `.to_string()` on it. -See the ["Into Conversions In-Depth"](../../../guide/into-conversions-in-depth) page that shows the common patterns and antipatterns of `impl Into`. +See the [Into Conversions In-Depth](../../../guide/patterns/into-conversions-in-depth) page that shows the common patterns and antipatterns of `impl Into`. ## Examples ::: code-group -```rust [Struct field] +```rust [Struct] use bon::Builder; #[derive(Builder)] @@ -49,7 +48,7 @@ Example::builder() .build(); ``` -```rust [Free function argument] +```rust [Function] use bon::builder; #[builder] @@ -75,7 +74,7 @@ example() .call(); ``` -```rust [Associated method argument] +```rust [Method] use bon::bon; struct Example; diff --git a/website/src/reference/builder/member/name.md b/website/src/reference/builder/member/name.md index 20198b88..a121e669 100644 --- a/website/src/reference/builder/member/name.md +++ b/website/src/reference/builder/member/name.md @@ -1,14 +1,14 @@ # `name` -**Applies to:** +**Applies to:** Overrides the name of the member used in the builder's API. This is most useful when with struct syntax (`#[derive(Builder)]`) where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported on function arguments. -**Example:** +## Examples ::: code-group -```rust [Struct field] +```rust [Struct] use bon::Builder; #[derive(Builder)] @@ -22,7 +22,7 @@ Player::builder() .build(); ``` -```rust [Free function argument] +```rust [Function] use bon::builder; #[builder] @@ -36,7 +36,7 @@ player() .call(); ``` -```rust [Associated method argument] +```rust [Method] use bon::bon; struct Player { diff --git a/website/src/reference/builder/member/overwritable.md b/website/src/reference/builder/member/overwritable.md index 20ecd92a..d2d17269 100644 --- a/website/src/reference/builder/member/overwritable.md +++ b/website/src/reference/builder/member/overwritable.md @@ -1,5 +1,9 @@ # `overwritable` :microscope: +**Applies to:** + +Allows calling setters for the same member repeatedly. + ::: danger 🔬 **Experimental** This attribute is available under the cargo feature `experimental-overwritable`. Breaking changes may occur between **minor** releases but not between patch releases. @@ -14,8 +18,6 @@ This attribute is also configurable via the top-level [`#[builder(on(...))]`](.. ::: -Lets calling setters for the same member repeatedly. - For example, this code wouldn't compile without this attribute: ```rust @@ -36,7 +38,7 @@ Overwrites like in the example above are generally considered bugs. However, the ## Improving compile times -This attribute simplifies the generated code. For example, it completely removes type states for [optional members](../../../guide/optional-members). +This attribute simplifies the generated code. For example, it completely removes type states for [optional members](../../../guide/basics/optional-members). If you'd like to improve your compile times, consider enabling overwrites with `#[builder(on(_, overwritable))]` and checking how much it helps. The difference is visible on a larger scale, especially for structs/functions with tens of optional members. @@ -46,11 +48,9 @@ It is a trade-off between the level of compile-time checks and compilation perfo You might want to use this to construct dummy values for tests (fixtures). -**Example:** - ::: tip -The builder's type signature mentioned in this example is described in the ["Builder Extensions"](../../../guide/builder-extensions) guide. +The builder's type signature mentioned in this example is described in the [Builder's Type Signature](../../../guide/typestate-api/builders-type-signature) guide. ::: diff --git a/website/src/reference/builder/member/setters.md b/website/src/reference/builder/member/setters.md index 85697df6..cfdd1279 100644 --- a/website/src/reference/builder/member/setters.md +++ b/website/src/reference/builder/member/setters.md @@ -1,5 +1,7 @@ # `setters` +**Applies to:** + Overrides name, visibility and docs for setters. The config is tree-structured with overrides precedence explained in the next paragraph. @@ -36,11 +38,11 @@ The config is tree-structured with overrides precedence explained in the next pa )] ``` -The main use case for this attribute is making generated setters private to wrap them with custom setters. See ["Builder extensions"](../../../guide/builder-extensions#custom-setters) for details. +The main use case for this attribute is making generated setters private to wrap them with custom methods. See [Custom Methods](../../../guide/typestate-api/custom-methods) for details. ## Config precedence -The keys `some_fn` and `option_fn` are available only for optional members that have a [pair of setters](../../../guide/optional-members#setters-pair). +The keys `some_fn` and `option_fn` are available only for optional members that have a [pair of setters](../../../guide/basics/optional-members#setters-pair). The root-level `name`, `vis`, `docs` are still available for both required and optional setters. They can be overwritten at `some_fn` and `option_fn` level individually. @@ -69,13 +71,13 @@ The default name for setters is chosen according to the following rules: | Required | `{member}` | Optional | `some_fn` = `{member}`
`option_fn` = `maybe_{member}` -This attribute is different from [`#[builder(name)]`](./name), because it overrides only the names of setters. It doesn't influence the name of the member in the builder's [typestate API](../typestate-api). This attribute also has higher precedence than [`#[builder(name)]`](./name). +This attribute is different from [`#[builder(name)]`](./name), because it overrides only the names of setters. It doesn't influence the name of the member in the builder's [typestate API](../../../guide/typestate-api). This attribute also has higher precedence than [`#[builder(name)]`](./name). ## `vis` The visibility 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 is the same as the visibility of the [`builder_type`](../top-level/builder-type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. +The default visibility is the same as the visibility of the [`builder_type`](../top-level/builder_type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. ## `doc` diff --git a/website/src/reference/builder/member/skip.md b/website/src/reference/builder/member/skip.md index 5f83b639..d7f48bda 100644 --- a/website/src/reference/builder/member/skip.md +++ b/website/src/reference/builder/member/skip.md @@ -11,7 +11,7 @@ The value for the member will be computed based on the form of the attribute: | `#[builder(skip)]` | `Default::default()` | | `#[builder(skip = expression)]` | `expression` | -**Example:** +## Example ```rust use bon::Builder; @@ -34,33 +34,35 @@ assert_eq!(user.level, 0); assert_eq!(user.name, "anon"); ``` -You can also use the values of other members by referencing their names in the `skip` expression. All members are initialized in the order of their declaration. It means only those members that are declared earlier (higher) in the code are available to the `skip` expression. +## Evaluation context -**Example:** +You can use values of other members by referencing their names in the `skip` expression. All members are initialized in the order of their declaration. It means only those members that are declared earlier (higher) in the code are available to the `skip` expression. ```rust use bon::Builder; #[derive(Builder)] struct Example { - member_1: u32, + x1: u32, - // Note that here we don't have access to `member_3` + // Note that here we don't have access to `x3` // because it's declared (and thus initialized) later - #[builder(skip = 2 * member_1)] - member_2: u32, + #[builder(skip = 2 * x1)] + x2: u32, - #[builder(skip = member_2 + member_1)] - member_3: u32, + #[builder(skip = x2 + x1)] + x3: u32, } let example = Example::builder() - .member_1(3) + .x1(3) .build(); -assert_eq!(example.member_1, 3); -assert_eq!(example.member_2, 6); -assert_eq!(example.member_3, 9); +assert_eq!(example.x1, 3); +assert_eq!(example.x2, 6); +assert_eq!(example.x3, 9); ``` -This attribute is not supported with free function arguments or associated method arguments because it's simply unnecessary there and can easier be expressed with local variables. +## Unsupported function syntax + +This attribute is not supported with function or method syntax because it's simply unnecessary there and can easier be expressed with local variables. diff --git a/website/src/reference/builder/member/start-fn.md b/website/src/reference/builder/member/start-fn.md deleted file mode 100644 index a633361f..00000000 --- a/website/src/reference/builder/member/start-fn.md +++ /dev/null @@ -1,106 +0,0 @@ -# `start_fn` - -**Applies to:** - -Makes the member a positional argument on the starting function that creates the builder. - -The ordering of members annotated with `#[builder(start_fn)]` matters! They will appear in the same order relative to each other in the starting function signature. They must also be declared at the top of the members' list. - -This ensures a consistent initialization order, and it makes these members available for expressions in `#[builder(default/skip = ...)]` for regular members that follow them. - -::: tip - -Don't confuse this with the top-level [`#[builder(start_fn = ...)]`](#start-fn) attribute that can be used to configure the name, visibility and docs of the starting function. You'll likely want to use it in combination with this member-level attribute to define a better name for the starting function. - -::: - -**Example:** - -::: code-group - -```rust [Struct field] -use bon::Builder; - -#[derive(Builder)] -// Top-level attribute to give a better name for the starting function // [!code highlight] -#[builder(start_fn = with_coordinates)] // [!code highlight] -struct Treasure { - // Member-level attribute to mark the member as // [!code highlight] - // a parameter of `with_coordinates()` // [!code highlight] - #[builder(start_fn)] // [!code highlight] - x: u32, - - #[builder(start_fn)] // [!code highlight] - y: u32, - - label: Option, -} - -let treasure = Treasure::with_coordinates(2, 9) // [!code highlight] - .label("knowledge".to_owned()) - .build(); - -assert_eq!(treasure.x, 2); -assert_eq!(treasure.y, 9); -assert_eq!(treasure.label.as_deref(), Some("knowledge")); -``` - -```rust [Free function] -use bon::builder; - -#[builder] -fn mark_treasure_at( - #[builder(start_fn)] // [!code highlight] - x: u32, - - #[builder(start_fn)] // [!code highlight] - y: u32, - - label: Option, -) {} - -mark_treasure_at(2, 9) - .label("knowledge".to_owned()) - .call(); -``` - -```rust [Associated method] -use bon::bon; - -struct Navigator {} - -#[bon] -impl Navigator { - #[builder] - fn mark_treasure_at( - &self, - - #[builder(start_fn)] // [!code highlight] - x: u32, - - #[builder(start_fn)] // [!code highlight] - y: u32, - - label: String, - ) {} -} - -let navigator = Navigator {}; - -navigator - .mark_treasure_at(2, 9) - .label("knowledge".to_owned()) - .call(); -``` - -::: - -You can also combine this attribute with [`#[builder(into)]`](./into) or [`#[builder(on(..., into))]`](../top-level/on) to add an into conversion for the parameter. - -Importantly, `Into` conversions for such members work slightly differently from the regular (named) members in regard to the `Option` type. The `Option` type gives no additional meaning to the member annotated with `#[builder(start_fn)]`. Thus, it is matched by the type pattern of `on(..., into)` and wrapped with `impl Into>` as any other type. - -::: tip - -In general, it's not recommended to annotate optional members with `#[builder(start_fn)]` because you can't omit setting them using the positional function call syntax. - -::: diff --git a/website/src/reference/builder/member/start_fn.md b/website/src/reference/builder/member/start_fn.md new file mode 100644 index 00000000..e9af3463 --- /dev/null +++ b/website/src/reference/builder/member/start_fn.md @@ -0,0 +1,113 @@ +# `start_fn` + +**Applies to:** + +Makes the member a positional argument on the starting function that creates the builder. + +::: tip + +Don't confuse this with the [top-level](../top-level/start_fn) `#[builder(start_fn)]` attribute. + +::: + +::: code-group + +```rust [Struct] +use bon::Builder; + +#[derive(Builder)] +struct Example { + #[builder(start_fn)] // [!code highlight] + x1: u32, + + #[builder(start_fn)] // [!code highlight] + x2: u32, + + x3: u32, +} + +let value = Example::builder(1, 2) // [!code highlight] + .x3(3) + .build(); + +assert_eq!(value.x1, 1); +assert_eq!(value.x2, 2); +assert_eq!(value.x3, 3); +``` + +```rust [Function] +use bon::builder; + +#[builder] +fn example( + #[builder(start_fn)] // [!code highlight] + x1: u32, + + #[builder(start_fn)] // [!code highlight] + x2: u32, + + x3: u32, +) -> (u32, u32, u32) { + (x1, x2, x3) +} + +let value = example(1, 2) // [!code highlight] + .x3(3) + .call(); + +assert_eq!(value.0, 1); +assert_eq!(value.1, 2); +assert_eq!(value.2, 3); +``` + +```rust [Method] +use bon::bon; + +struct Example; + +#[bon] +impl Example { + #[builder] + fn example( + #[builder(start_fn)] // [!code highlight] + x1: u32, + + #[builder(start_fn)] // [!code highlight] + x2: u32, + + x3: u32, + ) -> (u32, u32, u32) { + (x1, x2, x3) + } +} + +let value = Example::example(1, 2) // [!code highlight] + .x3(3) + .call(); + +assert_eq!(value.0, 1); +assert_eq!(value.1, 2); +assert_eq!(value.2, 3); +``` + +::: + +You can rename the starting function from default `builder()` for structs to something more readable via the top-level [`#[builder(start_fn = ...)]`](../top-level/start_fn) attribute. + +## Ordering + +The ordering of members annotated with `#[builder(start_fn)]` matters! They will appear in the same order relative to each other in the starting function signature. They must also be declared at the top of the members' list. + +This ensures a consistent initialization order, and it makes these members available for expressions in `#[builder(default/skip = ...)]` for regular members that follow them. + +## `Into` Conversions + +You can combine this attribute with [`#[builder(into)]`](./into) or [`#[builder(on(..., into))]`](../top-level/on) to add an `Into` conversion for the parameter. + +Importantly, `Into` conversions for such members work slightly differently from the regular (named) members in regard to the `Option` type. There is no special handling of `Option` type for the members annotated with `#[builder(start_fn)]`. Thus, they are matched by the type pattern of `on(..., into)` as any other type. + +::: tip + +In general, it's not recommended to annotate members of `Option` type with `#[builder(start_fn)]` because you can't omit setting them using the positional function call syntax. + +::: diff --git a/website/src/reference/builder/member/transparent.md b/website/src/reference/builder/member/transparent.md index 5f30adcd..8d57ac85 100644 --- a/website/src/reference/builder/member/transparent.md +++ b/website/src/reference/builder/member/transparent.md @@ -1,3 +1,40 @@ # `transparent` -TODO: add docs (update the short descriptions on the parent page) +**Applies to:** + +Disables `Option` special handling, makes the member required. + +::: tip + +This attribute is also configurable via the top-level [`#[builder(on(...))]`](../top-level/on). Currently, it can only be used with the `_` type pattern and as the first `on(...)` clause. + +::: + + +## Examples + +::: code-group + +```rust [Struct] +use bon::Builder; + +#[derive(Builder)] +struct Example { + #[builder(transparent)] + required: Option, + + optional: Option, +} + +Example::builder() + .required(Some(2)) + .optional(2) + .build(); +``` + +Notice the difference: + +| Member name | Setters | Comment +|-------------|--------------------------------------------------|------------ +| `required` | `required(Option)` | Setter is required to call +| `optional` | `optional(u32)`
`maybe_optional(Option)` | Setters are optional to call diff --git a/website/src/reference/builder/member/with.md b/website/src/reference/builder/member/with.md index b32e6bc0..545df081 100644 --- a/website/src/reference/builder/member/with.md +++ b/website/src/reference/builder/member/with.md @@ -1,5 +1,48 @@ # `with` +**Applies to:** + TODO: add docs (update the short descriptions on the parent page) + +If the closure accepts a single parameter `T`, then the `maybe_` setter accepts `Option`. Tuple is unnecessary in this case. + +You can use `impl Trait` for parameters in the closure, even though you can't in the usual Rust code. + + + +## Optional members + + + + ## Well-Known Functions + + +## Fallible setters + +You can add a return type annotation to the closure to signify that it's fallible. In this case a fallible setter will be generated. + +```rust +use bon::Builder; +use std::num::ParseIntError; + +#[derive(Builder)] +struct Example { + #[builder(with = |string: &str| -> Result<_, ParseIntError> { // [!code focus] + string.parse() // [!code focus] + })] // [!code focus] + x1: u32, // [!code focus] +} + +fn main() -> Result<(), ParseIntError> { + Example::builder() + .x1("99")? // <-- the setter returns a `Result` // [!code focus] + .build(); + + Ok(()) +} +``` diff --git a/website/src/reference/builder/top-level/builder-type.md b/website/src/reference/builder/top-level/builder_type.md similarity index 93% rename from website/src/reference/builder/top-level/builder-type.md rename to website/src/reference/builder/top-level/builder_type.md index b947257c..8ee1c999 100644 --- a/website/src/reference/builder/top-level/builder-type.md +++ b/website/src/reference/builder/top-level/builder_type.md @@ -4,7 +4,7 @@ outline: deep # `builder_type` -**Applies to:** +**Applies to:** Overrides name, visibility and docs for the builder struct. @@ -32,6 +32,8 @@ Overrides name, visibility and docs for the builder struct. The default name for the builder struct is chosen according to the following rules: + + | Syntax | Default | -----------------------------------|------------------------ | `struct T` | `{T}Builder` @@ -39,6 +41,8 @@ The default name for the builder struct is chosen according to the following rul | Associated `fn` `T::fn_name()` | `{T}{FnName}Builder` | Free `fn` `fn_name()` | `{FnName}Builder` + + ## `vis` The visibility 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. @@ -63,7 +67,7 @@ struct Brush {} let builder: MyBuilder = Brush::builder(); ``` -```rust [Free function] +```rust [Function] use bon::builder; #[builder(builder_type = MyBuilder)] // [!code highlight] @@ -72,7 +76,7 @@ fn brush() {} let builder: MyBuilder = brush(); ``` -```rust [Associated method] +```rust [Method] use bon::bon; struct Brush; diff --git a/website/src/reference/builder/top-level/crate.md b/website/src/reference/builder/top-level/crate.md index c15e9283..963d7ca1 100644 --- a/website/src/reference/builder/top-level/crate.md +++ b/website/src/reference/builder/top-level/crate.md @@ -1,12 +1,12 @@ # `crate` -**Applies to:** (*) +**Applies to:** (*) Overrides path to `bon` crate referenced in the generated code, which is useful in cases when `bon` macros are wrapped by other macros and `bon` crate is reexported. ::: info -(*) `#[builder(crate)]` attribute isn't directly supported for associated methods. Instead, you should use the `#[bon(crate)]` attribute on top of the impl block (see examples below). +(*) `#[builder(crate)]` attribute isn't accepted on associated methods. Instead, you should use the `#[bon(crate)]` attribute on top of the impl block (see examples below). ::: @@ -20,12 +20,12 @@ Overrides path to `bon` crate referenced in the generated code, which is useful struct Example {} ``` -```rust ignore [Free function] +```rust ignore [Function] #[::path::to::bon::builder(crate = ::path::to::bon)] fn example() {} ``` -```rust ignore [Associated method] +```rust ignore [Method] struct Example; #[::path::to::bon::bon(crate = ::path::to::bon)] diff --git a/website/src/reference/builder/top-level/derive.md b/website/src/reference/builder/top-level/derive.md index 7bfe2af3..b8908fda 100644 --- a/website/src/reference/builder/top-level/derive.md +++ b/website/src/reference/builder/top-level/derive.md @@ -1,6 +1,6 @@ # `derive` -**Applies to:** +**Applies to:** *⚠️ Do not confuse this with `#[derive(bon::Builder)]`⚠️* @@ -45,7 +45,7 @@ assert_eq!( let example = builder.is_admin(true).build(); ``` -```rust [Free function] +```rust [Function] use bon::builder; #[builder(derive(Clone, Debug))] // [!code highlight] @@ -73,7 +73,7 @@ assert_eq!( builder.is_admin(true).call(); ``` -```rust [Associated method] +```rust [Method] use bon::bon; #[derive(Debug)] diff --git a/website/src/reference/builder/top-level/finish-fn.md b/website/src/reference/builder/top-level/finish_fn.md similarity index 79% rename from website/src/reference/builder/top-level/finish-fn.md rename to website/src/reference/builder/top-level/finish_fn.md index 0163f04b..dc57ee0b 100644 --- a/website/src/reference/builder/top-level/finish-fn.md +++ b/website/src/reference/builder/top-level/finish_fn.md @@ -1,8 +1,14 @@ # `finish_fn` -**Applies to:** +**Applies to:** -Overrides name, visibility and docs for the builder's method that finishes the building process, i.e. returns the resulting object (in case of `#[derive(Builder)]` on a `struct`) or invokes the underlying function (in case of `#[builder]` on an `fn`). It is commonly referred to as the "finishing function". +Overrides name, visibility and docs for the finishing function. + +::: tip + +Don't confuse this with the [member-level](../member/finish_fn) `#[builder(finish_fn)]` attribute. + +::: **Short syntax** configures just the *name*. @@ -39,7 +45,7 @@ The default name for the finishing function is chosen according to the following The visibility 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 is the same as the visibility of the [`builder_type`](./builder-type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. +The default visibility is the same as the visibility of the [`builder_type`](./builder_type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. ## `doc` @@ -65,7 +71,7 @@ let article = Article::builder() assert_eq!(article.id, 42); ``` -```rust [Free function] +```rust [Function] use bon::builder; #[builder(finish_fn = send)] // [!code highlight] @@ -80,7 +86,7 @@ let response = get_article() assert_eq!(response, "Some article with id 42"); ``` -```rust [Associated method] +```rust [Method] use bon::bon; struct ArticlesClient; diff --git a/website/src/reference/builder/top-level/on.md b/website/src/reference/builder/top-level/on.md index bf1dab97..7fcc694c 100644 --- a/website/src/reference/builder/top-level/on.md +++ b/website/src/reference/builder/top-level/on.md @@ -4,7 +4,7 @@ outline: deep # `on` -**Applies to:** +**Applies to:** Applies member attributes to all members matching a type pattern. The syntax of this attribute is `on(type_pattern, attributes)`. For example, you can automatically apply `#[builder(into)]` to all members of type `String` this way: @@ -32,7 +32,7 @@ Example::builder() .build(); ``` -```rust [Free function] +```rust [Function] use bon::builder; #[builder(on(String, into))] @@ -53,7 +53,7 @@ example() .call(); ``` -```rust [Associated method] +```rust [Method] use bon::bon; struct Example; @@ -95,9 +95,9 @@ For optional members, the underlying type is matched ignoring the `Option` wrapp There are several attributes supported in the `attributes` position listed below. -- [`into`](../member/into); -- [`transparent`](../member/transparent) - currently, this attribute can only be used with the `_` type pattern as the first `on(...)` clause; -- [`overwritable`](../member/overwritable) - 🔬 **experimental**, this attribute is available under the cargo feature `"experimental-overwritable"` (see the issue [#149](https://github.com/elastio/bon/issues/149)); +- [`into`](../member/into) +- [`transparent`](../member/transparent) - currently, this attribute can only be used with the `_` type pattern as the first `on(...)` clause +- [`overwritable`](../member/overwritable) - 🔬 **experimental**, this attribute is available under the cargo feature `"experimental-overwritable"` (see the issue [#149](https://github.com/elastio/bon/issues/149)) A single `on(...)` clause can contain several of these separated by a comma e.g. `on(_, into, transparent)`. diff --git a/website/src/reference/builder/top-level/start-fn.md b/website/src/reference/builder/top-level/start_fn.md similarity index 91% rename from website/src/reference/builder/top-level/start-fn.md rename to website/src/reference/builder/top-level/start_fn.md index 19e5234a..2cbf9b21 100644 --- a/website/src/reference/builder/top-level/start-fn.md +++ b/website/src/reference/builder/top-level/start_fn.md @@ -1,8 +1,14 @@ # `start_fn` -**Applies to:** +**Applies to:** -Overrides name, visibility and docs for the function that starts the building process, i.e. returns the builder for the `struct` or `fn`. It is commonly referred to as the "starting function". +Overrides name, visibility and docs for the starting function. + +::: tip + +Don't confuse this with the [member-level](../member/start_fn) `#[builder(start_fn)]` attribute. + +::: When this attribute is used with `fn` syntax, it additionally [exposes the original function](#exposing-original-function) under the macro. @@ -41,7 +47,7 @@ The default name for the starting function is chosen according to the following The visibility 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 is the same as the visibility of the [`builder_type`](./builder-type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. +The default visibility is the same as the visibility of the [`builder_type`](./builder_type#vis), which in turn, defaults to the visibility of the underlying `struct` or `fn`. ## `doc` @@ -108,7 +114,7 @@ The [`name`](#name) parameter is **required** when `start_fn` is used with `fn` ::: code-group -```rust [Free function] +```rust [Function] use bon::builder; #[builder(start_fn = example_builder)] // [!code ++] @@ -124,7 +130,7 @@ example_builder() .call(); ``` -```rust [Associated method] +```rust [Method] use bon::bon; struct Example; diff --git a/website/src/reference/builder/top-level/state-mod.md b/website/src/reference/builder/top-level/state_mod.md similarity index 75% rename from website/src/reference/builder/top-level/state-mod.md rename to website/src/reference/builder/top-level/state_mod.md index 8490ab72..c3b15e19 100644 --- a/website/src/reference/builder/top-level/state-mod.md +++ b/website/src/reference/builder/top-level/state_mod.md @@ -1,3 +1,5 @@ # `state_mod` TODO: add docs + +## `doc` diff --git a/website/src/reference/builder/typestate-api.md b/website/src/reference/builder/typestate-api.md deleted file mode 100644 index fcc5bbea..00000000 --- a/website/src/reference/builder/typestate-api.md +++ /dev/null @@ -1,5 +0,0 @@ -# Typestate API - -Preliminary reading of ["Builder Extensions"](../../guide/builder-extensions) guide is recommended to understand how the pieces on this page fit together. - -TODO: add docs diff --git a/website/src/v2/guide/overview.md b/website/src/v2/guide/overview.md index 3cc3e501..149afc03 100644 --- a/website/src/v2/guide/overview.md +++ b/website/src/v2/guide/overview.md @@ -1,9 +1,3 @@ - - # Overview `bon` is a Rust crate for generating compile-time-checked builders for functions and structs. It also provides idiomatic partial application with optional and named parameters for functions and methods. @@ -14,9 +8,9 @@ If you are new to the concept of builders or named function arguments, and you d Add this to your `Cargo.toml` to use this crate: -```toml-vue +```toml [dependencies] -bon = "{{ versionWildcard }}" +bon = "2.3" ``` You can opt out of `std` and `alloc` cargo features with `default-features = false` for `no_std` environments. @@ -264,7 +258,7 @@ This is just part of what's available in `bon`. You may consider reading the res However, feel free to skip the docs and just use the `#[builder]` and `#[derive(Builder)]` in your code. They are designed to be intuitive, so they'll probably do the thing you want them to do already. -If you can't figure something out, consult the docs and maybe use that search `🔍 Search` thing at the top to navigate. You may also create an issue or a discussion in the [Github repository](https://github.com/elastio/bon) for help, or write us a message in [Discord](https://discord.gg/QcBYSamw4c) (see below). +If you can't figure something out, consult the docs and maybe use that search `🔍 Search` thing at the top to navigate. You may also create an issue or a discussion in the [Github repository](https://github.com/elastio/bon) for help, or write us a message in [Discord](https://bon-rs.com/discord) (see below). ## Socials @@ -275,9 +269,9 @@ If you can't figure something out, consult the docs and maybe use that search ` Here you can leave feedback, ask questions, report bugs, or just write "thank you". diff --git a/website/tsconfig.json b/website/tsconfig.json index 9d894aca..3fdd435a 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -1,8 +1,12 @@ { + "vueCompilerOptions": { + "vitePressExtensions": [".md"], + }, "include": [ "**/*.ts", "**/*.mts", "**/*.vue", + "**/*.md", ".vitepress/**/*.ts", ".vitepress/**/*.vue", ], @@ -30,5 +34,5 @@ }, "ts-node": { "esm": true - } + }, }