1 unstable release
0.1.0 | Oct 27, 2024 |
---|
#1975 in Rust patterns
9KB
builder!
A macro, or two, to simplify usage of builder pattern on structs.
use builder_option::Builder;
#[derive(Debug, Builder)]
pub struct Client {
pub field: bool,
pub another: Option<bool>,
pub optional: Option<String>,
}
fn main() -> Result<(), dyn std::error::Error> {
let client = Client::builder()
.with_field(true)
.with_another(false),
.build()?;
assert!(client.field);
assert_eq!(client.another, false);
assert!(client.optional.is_none());
}
LICENSE
lib.rs
:
This crate contains two macros to declare a struct and a corresponding builder.
builder!
: By-example macro that creates a struct and the builder.Builder
: Derive macro that creates a builder for annotated struct.
The macro-by-example syntax is inspired by azriel91/builder-macro, but with a different philosophy of treating required vs. optional fields, and therefore a different design choice.
Background
For usage, please skip ahead to the Usage section.
Thic crate was created as a sort-of practice in building macros in Rust, hence 2 similar approaches, one using proc-macro, and one using macro-by-example, that have identical syntax.
The key feature of the macro is that if a client struct defines a field as Option
, the builder's
API still takes an inner value in setters, but the build
process won't enforce those fields,
while it'll break if a value for regular, non-Option fields, is not provided.
Returning Result
allows the caller to handle failures gracefully.
Usage
builder!
Specify dependency in your crate's Cargo.toml
[dependencies]
builder_option = "0.1.0"
Include the macro inside your crate's lib.rs
or main.rs
.
use builder_option::builder;
derive(Builder)
The default version of crate defines only the builder!
macro-by-example. If you prefer the Builder
derive proc-macro
version, specify derive
feature:
[dependencies]
builder_option = { version = "0.1.0", features = [ "derive" ] }
Include the macro inside your crate's lib.rs
or main.rs
.
use builder_option::Builder;
Examples
A simpliest way to use the macro is to create a simple struct:
use builder_option::builder;
builder!(ClientBuilder -> Client {
pub required{bool},
})
fn main() -> Result<(), Box<dyn Error>> {
let client = Client::builder()
.with_required(true)
.build()?;
assert!(client.required);
}
You can see the funny C++-like initializer syntax for types: required{bool}
, this an unfortunate consequence of
Rust macro limitations.
A failure to provide a required parameter will cause an error:
use builder_option::builder;
builder!(ClientBuilder -> Client {
pub required{bool},
})
fn main() {
let client = Client::builder()
.build();
assert!(client.is_err());
}
Fields marked as optional—i.e., wrapped in an Option—are simplified in the builder API by unwrapping one level of Option.
This means that for a field declared as optional: Option<bool>
, the builder will provide a setter method set_optional(value: bool)
that accepts a bool instead of an Option. However, if a field has nested options like optional: Option<Option<bool>>
, the builder
unwrapping only the first level results in a setter method that accepts Option<bool>
.
use builder_option::builder;
builder!(ClientBuilder -> Client {
pub required{bool},
pub optional1{Option<bool>},
pub optional2{Option<Option<bool>>},
pub optional3{String},
})
fn main() -> Result<(), Box<dyn Error>> {
let client = Client::builder()
.with_required(true)
.with_optional1(true)
.with_optional2(Some(true))
.build()?;
assert!(client.required);
assert!(client.optional1.unwrap()?);
assert!(client.optional3.is_none());
}
Same result using derive proc-macro:
use builder_option::Builder;
#[derive(Debug, Builder)]
pub struct Client {
pub required: bool,
pub optional1: Option<bool>,
pub optional2: Option<Option<bool>>,
pub optional3: Option<String>
}
Dependencies
~105KB