Skip to content

Commit

Permalink
Merge pull request #58 from koenichiwa/release/0.3.0
Browse files Browse the repository at this point in the history
Release/0.3.0
  • Loading branch information
koenichiwa authored Oct 3, 2023
2 parents c8e8b39 + 19a8076 commit f15601c
Show file tree
Hide file tree
Showing 33 changed files with 1,047 additions and 502 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [".", "./const_typed_builder_derive", "./const_typed_builder_test"]

[workspace.package]
description = "Compile-time type-checked builder derive using const generics"
version = "0.2.0"
version = "0.3.0"
authors = ["Koenichiwa <[email protected]>"]
repository = "https://github.com/koenichiwa/const_typed_builder/"
edition = "2021"
Expand All @@ -27,7 +27,7 @@ readme.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.2.0" }
const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.3.0" }

[dev-dependencies]
trybuild = "1.0.84"
158 changes: 97 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,127 @@
# `Builder` Derive Macro Documentation

The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created.
The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created.

## Prerequisites
The library also checks cases where the struct can never be valid or is always valid, but this is still in a work in progress. Errors are always emitted, but warnings are emitted on the nightly channel only. This is due to a limitation in [proc_macro_error](https://docs.rs/proc-macro-error/latest/proc_macro_error/).

## Prerequisites

To use the `Builder` derive macro, you should have the `const_typed_builder` crate added to your project's dependencies in your `Cargo.toml` file:

```toml
[dependencies]
const_typed_builder = "0.2"
const_typed_builder = "0.3"
```

Also, make sure you have the following import statements in your code:
Also, make sure you have the following use statement in your code:

```rust
use const_typed_builder::Builder;
```
## Overview
## Overview
The crate can be used to check validity of structs at compile time. The user can't call build until the struct is valid. This is done by checking if all mandatory fields are instantiated and all user defined "groups" are valid.

### Simple example
This derive:
### Examples
Basic usage:
```rust
use const_typed_builder::Builder;
#[derive(Debug, Builder)]

#[derive(Builder)]
pub struct Foo {
bar: String,
}

let foo = Foo::builder()
.bar("Hello world!".to_string()) // <- The program would not compile without this call
.build(); // <- Because this function is only implemented for the
// .. version of FooBuilder where `bar` is initialized
```
Expands to this code:

Subset of launchd:
```rust
use const_typed_builder::Builder;
#[derive(Debug)]
pub struct Foo {
bar: String,
}
impl Builder for Foo {
type BuilderImpl = FooBuilder<false>;
fn builder() -> Self::BuilderImpl {
Self::BuilderImpl::new()
}
}
#[derive(Debug)]
pub struct FooBuilder<const M_0: bool> {
data: FooData,
}
impl FooBuilder<false> {
pub fn new() -> FooBuilder<false> {
Self::default()
}
}
impl Default for FooBuilder<false> {
fn default() -> Self {
FooBuilder {
data: FooData::default(),
}
}
}
impl FooBuilder<false> {
pub fn bar(self, bar: String) -> FooBuilder<true> {
let mut data = self.data;
data.bar = Some(bar);
FooBuilder { data }
}
}
impl FooBuilder<true> {
pub fn build(self) -> Foo {
self.data.into()
}
}
#[derive(Debug)]
pub struct FooData {
pub bar: Option<String>,
}
impl From<FooData> for Foo {
fn from(data: FooData) -> Foo {
Foo {
bar: data.bar.unwrap(),
}
}
use std::path::PathBuf;

#[derive(Builder)]
pub struct ResourceLimits {
core: Option<u64>,
// ...
}
impl Default for FooData {
fn default() -> Self {
FooData { bar: None }
}
#[derive(Builder)]
#[group(program = at_least(1))]
pub struct Launchd {
#[builder(mandatory)]
label: Option<String>,
disabled: Option<bool>,
user_name: Option<String>,
group_name: Option<String>,
// ...
#[builder(group = program)]
program: Option<PathBuf>,
bundle_program: Option<String>,
#[builder(group = program)]
program_arguments: Option<Vec<String>>,
// ...
#[builder(skip)]
on_demand: Option<bool>, // NB: deprecated (see KeepAlive), but still needed for reading old plists.
#[builder(skip)]
service_ipc: Option<bool>, // NB: "Please remove this key from your launchd.plist."
// ...
#[builder(propagate)]
soft_resource_limits: Option<ResourceLimits>,
#[builder(propagate)]
hard_resource_limits: Option<ResourceLimits>,
// ...
}

let launchd = Launchd::builder()
.label("my_label".to_string()) // <- 1: Mandatory
.program("./my_program".into()) // <- 2: Launchd expects that least one of these fields is set..
.program_arguments( // <- 2: .. We can remove either one, but never both
vec!["my_arg".to_string()]
)
// .on_demand(false) <- 3: This function doesn't exist
.soft_resource_limits(|builder|
Some(builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder`
)
.build();
```

### Attributes
This is a quick overview of the features in this library. See [`const_typed_builder_derive::Builder`] for a more in depth explanation of all the features, including examples.
**Struct**
- `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory.
If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true.
If provided with an equals sign (e.g., `#[builder(assume_mandatory = true)]`), it sets the `mandatory` flag for fields based on the value.
- `#[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]`:
Associates fields of the struct with a group named "group_name" and specifies the group's behavior.
The `group_name` should be a string identifier. The group can have one of the following behaviors:
- `exact(N)`: Exactly N fields in the group must be set during the builder construction.
- `at_least(N)`: At least N fields in the group must be set during the builder construction.
- `at_most(N)`: At most N fields in the group can be set during the builder construction.
- `single`: Only one field in the group can be set during the builder construction. This is a shorthand for `exact(1)`.
e.g `#[group(foo = at_least(2))]` creates a group where at least 2 of the fields need to be initialized.
- `#[builder(solver = (brute_force|compiler))]`: **Use sparingly, see note at bottom of this file!**
Specifies the solver type to be used for building the struct. The `solve_type` should be one of the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`),
it sets the "solver type" accordingly. This attribute is still tested, and `brute_force` is the default, and only if there are problems in compilation time then you can try `compiler`. `compiler` gives less guarantees though.

**Field**
- `#[builder(group = group_name)]`: The heart of this library. This associates the field with a group named `group_name`.
Fields in the same group are treated as a unit, and at least one of them must be set during builder construction. This attribute allows specifying the group name both as an identifier (e.g., `group = my_group`)
and as a string (e.g., `group = "my_group"`).
- `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder
construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory.
If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value.
- `#[builder(optional)]`: Marks the field as optional, this is the exact opposite of `#[builder(mandatory)]`.
If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional.
If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value.
- `#[builder(skip)]`: Marks the field as skipped, meaning that the builder will not include it. This can be used for
fields that are deprecated, but must still be deserialized. This way you can ensure that new structs will never be created with this field initialized, but that old structs can still be used. The field type has to be `Option<T>` for this to work.
- `#[builder(propagate)]`: Indicates that the field should propagate its value when the builder is constructed.
If this attribute is present, the field's value will be copied or moved to the constructed object when the builder is used to build the object.

Fields can either be a part of a group, mandatory, optional OR skipped. These attribute properties are mutually exclusive. `propagate` can be used on any field where the type also derives `Builder`.

> [!NOTE]
> Checking the validity of each field is a problem directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), which is an NP-complete problem. This has effect especially the grouped fields. The current default implementation for checking the validity of grouped fields is `brute_force`, and this implementation currently has a complexity of $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. This can be still optimized significantly. Future editions might improve on this complexity.
>
Expand Down
2 changes: 2 additions & 0 deletions const_typed_builder_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ quote = "1.0"
proc-macro2 = "1.0"
either = "1.9"
itertools = "0.11.0"
convert_case = "0.6"
proc-macro-error = "1.0"

[lib]
proc-macro = true
Loading

0 comments on commit f15601c

Please sign in to comment.