#builder-pattern #design-pattern #pattern #type-state #builder #state #macro-derive

typestate-builder

Derive-macro-based generator that combines Typestate and Builder patterns

3 releases

0.1.4 Oct 30, 2024
0.1.3 Oct 30, 2024
0.1.1 Sep 25, 2024

#1763 in Rust patterns

Download history 132/week @ 2024-09-23 27/week @ 2024-09-30 2/week @ 2024-10-07 250/week @ 2024-10-28

250 downloads per month

MIT/Apache

28KB
543 lines

typestate-builder

TypestateBuilder is a Rust procedural macro that enables the creation of builder patterns using the typestate design pattern. This macro ensures that your structs are built in a way that enforces compile-time safety, ensuring that required fields are initialized before the struct is created.

For more informatiion, read the document.

License

TypestateBuilder is dual-licensed under the MIT and Apache 2.0 licenses. See the LICENSE-MIT and LICENSE-APACHE files for details.


lib.rs:

typestate-builder

TypestateBuilder is a Rust procedural macro that enables the creation of builder patterns using the typestate design pattern. This macro ensures that your structs are built in a way that enforces compile-time safety, ensuring that required fields are initialized before the struct is created.

Table of Contents

Features

  • Enforced Typestate Pattern: Leveraging Rust's type system, the macro ensures that required fields are set before a struct can be created.
  • No Runtime Checks: The macro generates the necessary types and performs checks at compile time, eliminating the need for Option or Result types.
  • Compile-Time Safety: All required fields must be initialized before creating an instance of the struct, promoting safer code.
  • Support for Named and Tuple Structs: Works seamlessly with both named and tuple structs, with clear error messages for unsupported configurations.
  • Fluent and Intuitive Syntax: Offers a simple and idiomatic Rust syntax for creating builders, enhancing code readability and usability.

Example

Here’s a basic example demonstrating how to use the TypestateBuilder macro:

use typestate_builder::TypestateBuilder;

#[derive(Debug, TypestateBuilder)] // `Debug` is not a must.
struct Person {
name: String,
age: u32,
email: Option<String>,
}

let person = Person::builder()
.name("Alice Johnson".to_string())
.age(30)
.email(Some("alice@example.com".to_string()))
.build();
println!("Created person: {:?}", person);

In this example, the Person struct uses the TypestateBuilder derive macro to create a builder. Each field can be set in a fluent interface style, and the build method assembles the Person instance once all required fields have been set.

How It Works

  • State Management: The macro generates intermediate state structs for each field of the struct being built. Each state struct represents a stage in the building process.

  • Method Chaining: For each field, a method is generated that accepts a value for that field and returns a new builder state, ensuring that only valid transitions are allowed.

  • Final Assembly: The build method assembles the final struct once all required fields have been set, preventing the creation of incomplete instances.

  • Graph analysis: This crate internally analyzes the sub-elements of structures with graph and describes the relationships between them. Thus, the source of the final generated code is derived from this analysis.

Code Expanded

The expanded version of the above code is like this:

struct PersonBuilder<NameGenericParam, AgeGenericParam, EmailGenericParam> {
name: NameGenericParam,
age: AgeGenericParam,
email: EmailGenericParam,
}
struct PersonBuilderNameEmpty;
struct PersonBuilderNameAdded(String);
struct PersonBuilderAgeEmpty;
struct PersonBuilderAgeAdded(u32);
struct PersonBuilderEmailEmpty;
struct PersonBuilderEmailAdded(Option<String>);
impl Person {
fn builder() -> PersonBuilder<
PersonBuilderNameEmpty,
PersonBuilderAgeEmpty,
PersonBuilderEmailEmpty,
> {
PersonBuilder {
name: PersonBuilderNameEmpty,
age: PersonBuilderAgeEmpty,
email: PersonBuilderEmailEmpty,
}
}
}
impl<
AgeGenericParam,
EmailGenericParam,
> PersonBuilder<PersonBuilderNameEmpty, AgeGenericParam, EmailGenericParam> {
fn name(
self,
name: String,
) -> PersonBuilder<PersonBuilderNameAdded, AgeGenericParam, EmailGenericParam> {
PersonBuilder {
name: PersonBuilderNameAdded(name),
age: self.age,
email: self.email,
}
}
}
impl<
NameGenericParam,
EmailGenericParam,
> PersonBuilder<NameGenericParam, PersonBuilderAgeEmpty, EmailGenericParam> {
fn age(
self,
age: u32,
) -> PersonBuilder<NameGenericParam, PersonBuilderAgeAdded, EmailGenericParam> {
PersonBuilder {
name: self.name,
age: PersonBuilderAgeAdded(age),
email: self.email,
}
}
}
impl<
NameGenericParam,
AgeGenericParam,
> PersonBuilder<NameGenericParam, AgeGenericParam, PersonBuilderEmailEmpty> {
fn email(
self,
email: Option<String>,
) -> PersonBuilder<NameGenericParam, AgeGenericParam, PersonBuilderEmailAdded> {
PersonBuilder {
name: self.name,
age: self.age,
email: PersonBuilderEmailAdded(email),
}
}
}
impl PersonBuilder<
PersonBuilderNameAdded,
PersonBuilderAgeAdded,
PersonBuilderEmailAdded,
> {
fn build(self) -> Person {
Person {
name: self.name.0,
age: self.age.0,
email: self.email.0,
}
}
}

Possible Limitations

In fact, I’m not entirely sure what the upper limit is for the most complex struct that this crate can handle. However, I’ve added dozens of complex structs here for testing purposes, and the crate successfully handles all of them. If you have any new ideas for testing structs, feel free to send me a PR.

License

TypestateBuilder is dual-licensed under the MIT and Apache 2.0 licenses. See the LICENSE-MIT and LICENSE-APACHE files for details. Derive-macro-based generator that combines Typestate and Builder patterns.

Dependencies

~2.8–4MB
~70K SLoC