Crates.io | quick-builder |
lib.rs | quick-builder |
version | 0.1.2 |
source | src |
created_at | 2024-10-04 10:02:28.366623 |
updated_at | 2024-10-28 17:40:56.357181 |
description | compile-time checked builder generator with run-time invariant enforcement |
homepage | https://github.com/geo-ant/quick-builder |
repository | https://github.com/geo-ant/quick-builder |
max_upload_size | |
id | 1396349 |
size | 9,157 |
This crate is deprecated, since its use case of fallible builders is better served by the bon crate.
This crate offers a simple, but powerful, compile-time builder pattern generator. The philosophy is to verify as much as possible at compile-time, while also providing a straightforward way to enforce run-time invariants.
Give QuickBuilder a shot if you want to derive a builder for your struct, that
There are great builder crates, like bon
or typed-builder, that allow you
to create idiomatic builders enforcing that all necessary fields
have been set at compile-time. Like those crates, QuickBuilder
can generate a builder that only allows to call the final .build()
method if
all fields were initialized.
use quick_builder::QuickBuilder;
#[derive(QuickBuilder)]
struct ImageRef<'a, T> {
width: usize,
height: usize,
data: &'a [T],
}
fn main() {
let image_data = &[1, 2, 3, 4, 5, 6];
let imgref = ImageRef::builder()
.width(3)
.height(2)
.data(image_data)
.build();
}
However, the example above is not the main usecase for QuickBuilder. If that's all you ever need to do, check out the bon or typed-builder crates. Those offer, among other things, more exhaustive support for idioms like optional and default parameters, as well as great ergonomics. QuickBuilder shines when you additionally need to enforce run-time invariants about your data structure.
In the example above we might want to enforce a couple of invariants about the data structure which we can only check at run-time. The following example shows, how we can use QuickBuilder to enforce that...
use quick_builder::QuickBuilder;
#[derive(QuickBuilder)]
#[invariant(|my| my.width * my.height == my.data.len())]
struct ImageRef<'a, T> {
#[invariant(|w|*w>0)]
width: usize,
#[invariant(check_height)]
height: usize,
data: &'a [T],
}
// if the conditions to check invariants are too
// unwieldy to put into a closure, you can also
// define a standalone function.
fn check_height(height :&usize) -> bool {
*height > 0 && *height % 2 == 0
}
fn main() {
let image_data = &[1, 2, 3, 4, 5, 6];
let imgref = ImageRef::builder()
.width(3)
.height(2)
.data(image_data)
.build()
.unwrap();
}
One (or zero) #[invariant(...)]
attributes can be applied to each field or
to the struct itself. The attributes take a closure or function name to check
if the invariant holds. The function (or closure) must take its
argument by reference and return a bool
, where true
means that the invariant
holds and false
means it's violated.
As soon as an #[invariant(...)]
attribute is encountered, the build
function
changes its signature. It now returns an optional instance of the original
structure, where the optional contains a value if and only if all invariants
where upheld during construction.
bon
and typed-builder
crates allow arbitrary
orders, but they don't have a mechanism for enforcing run-time invariants.If care about enforcing invariants about your data, you probably want to provide getters to your fields rather than making them publicly accessible. In that case, you'll be happy to hear that this crate works seamlessly with the popular getset and derive-getters crates. Those offer derive macros for getters and setters.
There is a great overview of builder crates
by the bon
team. Of the compile-time builders in this list, to my knowledge, only the bon
crate provides a way to enforce run-time invariants.