3 releases
0.1.4 | Oct 30, 2024 |
---|---|
0.1.3 | Oct 30, 2024 |
0.1.1 | Sep 25, 2024 |
#1807 in Rust patterns
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
~69K SLoC