Crate const_builder

Source
Expand description

Provides a ConstBuilder derive macro that generates a *Builder type that can be used to create a value with the builder pattern, even in a const context.

The attributed type will gain an associated builder method, which can then be chained with function calls until all required fields are set, at which point you can call build to get the final value.

Compile-time checks prevent setting the same field twice or calling build before all required fields are set.

By default, the *Builder type and *::builder method have the same visibility as the type, and every field setter is pub, regardless of field visibility.

There is also an *UncheckedBuilder without safety checks, which is private by default. You can convert between them with *Builder::into_unchecked and *UncheckedBuilder::assert_init.

Whichever builder you use, it isn’t clonable and its methods are called by-value, returning the updated builder value.

The generated code is supported in #![no_std] crates.

§Unsafety

This derive macro generates unsafe code using MaybeUninit to facilitate field-wise initialization of a struct, tracking initialized fields via const-generics. This broadly follows the guidance in the nomicon section on unchecked uninitialized memory, and is, for now, required to get const-compatible builders for arbitrary types.

§Struct Requirements

All struct fields must be Sized. Fields using generic parameters may be ?Sized for some parameters, as long as the actual instantiation of the builder only has Sized fields.

When the struct is #[repr(packed)] and the last field may be ?Sized, the field needs to be attributed with #[builder(unsized_tail)] to replace the field drop code with an assert that the field cannot be dropped. Rust functionally currently requires this combination of packed and unsized tails to be ManuallyDrop or a wrapper around it.

enum and union types are unsupported.

§Default Values

Fields can be attributed with #[builder(default = None)] or similar to be made optional by providing a default value.

This default value will not be dropped if it is overridden or the builder is dropped. Consequently, the value should be something that does not need to be dropped, such as a primitive, None, String::new(), Vec::new(), Cow::Borrowed, or similar.

§Example

use const_builder::ConstBuilder;

#[derive(ConstBuilder)]
pub struct Person<'a> {
    // fields are required by default
    pub name: &'a str,
    // optional fields have a default specified
    // the value is required even when the type implements `Default`!
    #[builder(default = 0)]
    pub age: u32,
}

let steve = const {
    Person::builder()
        .name("steve smith")
        .build()
};

§Generated Interface

The example above would generate an interface similar to the following. The actual generated code is more complex because it includes bounds to ensure fields are only written once and that the struct is fully initialized when calling build.

/// A builder type for [`Person`].
pub struct PersonBuilder<'a, const _NAME: bool = false, const _AGE: bool = false> { ... }

impl<'a, ...> PersonBuilder<'a, ...> {
    /// Creates a new builder.
    pub const fn new() -> Self;

    /// Returns the finished value.
    ///
    /// This function can only be called when all required fields have been set.
    pub const fn build(self) -> Person<'a>;

    // one setter function per field
    pub const fn name(self, value: &'a str) -> PersonBuilder<'a, ...>;
    pub const fn age(self, value: u32) -> PersonBuilder<'a, ...>;

    /// Unwraps this builder into its unsafe counterpart.
    ///
    /// This isn't unsafe in itself, however using it carelessly may lead to
    /// leaking objects and not dropping initialized values.
    const fn into_unchecked(self) -> PersonUncheckedBuilder<'a>;
}

impl<'a> Person<'a> {
    /// Creates a new builder for this type.
    pub const fn builder() -> PersonBuilder<'a>;
}

/// An _unchecked_ builder type for [`Person`].
///
/// This version being _unchecked_ means it has less safety guarantees:
/// - No tracking is done whether fields are initialized, so [`Self::build`] is `unsafe`.
/// - If dropped, already initialized fields will be leaked.
/// - The same field can be set multiple times. If done, the old value will be leaked.
struct PersonUncheckedBuilder<'a> { ... }

impl<'a> PersonUncheckedBuilder<'a> {
   /// Creates a new unchecked builder.
   pub const fn new() -> Self;

   /// Asserts that the fields specified by the const generics are initialized
   /// and promotes this value into a checked builder.
   ///
   /// # Safety
   ///
   /// The fields whose const generic is `true` and optional fields have to be
   /// initialized.
   ///
   /// Optional fields are initialized by [`Self::new`] by default, however using
   /// [`Self::as_uninit`] allows de-initializing them.
   const unsafe fn assert_init<const _NAME: bool, const _AGE: bool>(self) -> PersonBuilder<'a, _NAME, _AGE>;

   /// Returns the finished value.
   ///
   /// # Safety
   ///
   /// This function requires that all fields have been initialized.
   pub const unsafe fn build(self) -> Person<'a>;

   // one setter function per field
   pub const fn name(mut self, value: &'a str) -> PersonUncheckedBuilder<'a>;
   pub const fn age(mut self, value: u32) -> PersonUncheckedBuilder<'a>;

   /// Gets a mutable reference to the partially initialized data.
   pub const fn as_uninit(&mut self) -> &mut ::core::mem::MaybeUninit<Person<'a>>;
}

§Struct Attributes

These attributes can be specified within #[builder(...)] on the struct level.

AttributeMeaning
defaultGenerate a const-compatible *::default() function and Default derive. Requires every field to have a default value.
visChange the visibility of the builder type. May be an empty string for private. Default is the same as the struct.
renameRenames the builder type. Defaults to "<Type>Builder".
rename_fnRenames the associated function that creates the builder. Defaults to builder. Set to false to disable.
unchecked(vis)Change the visibility of the unchecked builder type. Default is private.
unchecked(rename)Renames the unchecked builder type. Defaults to "<Type>UncheckedBuilder".

§Field Attributes

These attributes can be specified within #[builder(...)] on the struct’s fields.

AttributeMeaning
visChange the visibility of the builder’s field setter. May be an empty string for private. Default is pub.
defaultMake the field optional by providing a default value.
renameRenames the setters for this field. Defaults to the field name.
rename_genericRenames the name of the associated const generic. Defaults to _{field:upper}.
leak_on_dropInstead of dropping the field when dropping the builder, do nothing.
unsized_tailIn a packed struct, marks the last field as potentially being unsized, replacing the drop code with an assert. No effect if the struct isn’t packed.
setter(transform)Accepts closure syntax. The setter is changed to accept its inputs and set the corresponding value to its output.
setter(strip_option)On an Option<T> field, change the setter to accept T and wrap it in Some itself. Equivalent to setter(transform = |value: T| Some(value)).

§Attributes Example

use const_builder::ConstBuilder;

#[derive(ConstBuilder)]
// change the builder from pub (same as Person) to crate-internal
// also override the name of the builder to `CreatePerson`
#[builder(vis = "pub(crate)", rename = "CreatePerson")]
// change the unchecked builder from priv also to crate-internal
#[builder(unchecked(vis = "pub(crate)"))]
pub struct Person<'a> {
    // required field with public setter
    name: &'a str,
    // optional field with public setter
    #[builder(default = 0)]
    age: u32,
    // optional field with private setter
    #[builder(default = 1, vis = "" /* priv */)]
    version: u32,
}

Derive Macros§

ConstBuilder
Generates the builder types for the attributed struct.