4 releases
0.1.4 | Oct 30, 2024 |
---|---|
0.1.3 | Oct 30, 2024 |
0.1.2 | Oct 30, 2024 |
0.1.1 | Sep 25, 2024 |
#12 in #type-state
Used in typestate-builder
110KB
2.5K
SLoC
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
orResult
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 on the way) Structs: Works with named structs, with 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.
Code Expanded
The expanded version of the above code is like this:
struct PersonBuilderNameAdded(String);
struct PersonBuilderNameEmpty;
struct PersonBuilderAgeAdded(u32);
struct PersonBuilderAgeEmpty;
struct PersonBuilderEmailAdded(Option<String>);
struct PersonBuilderEmailEmpty;
pub struct PersonBuilder<Name, Age, Email> {
name: Name,
age: Age,
email: Email,
}
impl Person {
pub fn builder() -> PersonBuilder<
PersonBuilderNameEmpty,
PersonBuilderAgeEmpty,
PersonBuilderEmailEmpty,
> {
PersonBuilder {
name: PersonBuilderNameEmpty,
age: PersonBuilderAgeEmpty,
email: PersonBuilderEmailEmpty,
}
}
}
impl<Age, Email> PersonBuilder<PersonBuilderNameEmpty, Age, Email> {
pub fn name(
self,
name: String,
) -> PersonBuilder<PersonBuilderNameAdded, Age, Email> {
PersonBuilder {
name: PersonBuilderNameAdded(name),
age: self.age,
email: self.email,
}
}
}
impl<Name, Email> PersonBuilder<Name, PersonBuilderAgeEmpty, Email> {
pub fn age(self, age: u32) -> PersonBuilder<Name, PersonBuilderAgeAdded, Email> {
PersonBuilder {
name: self.name,
age: PersonBuilderAgeAdded(age),
email: self.email,
}
}
}
impl<Name, Age> PersonBuilder<Name, Age, PersonBuilderEmailEmpty> {
pub fn email(
self,
email: Option<String>,
) -> PersonBuilder<Name, Age, PersonBuilderEmailAdded> {
PersonBuilder {
name: self.name,
age: self.age,
email: PersonBuilderEmailAdded(email),
}
}
}
impl PersonBuilder<
PersonBuilderNameAdded,
PersonBuilderAgeAdded,
PersonBuilderEmailAdded,
> {
pub fn build(self) -> Person {
Person {
name: self.name.0,
age: self.age.0,
email: self.email.0,
}
}
}
Limitations
In fact, i really can't know (and no one will ever know) where is the final frontier of this crate. But i pasted dozens complex structs here to have them as for setting goals, not all of them can be expected to pass the tests. Therefore, it's impossible to give a guarantee in a universe where black can be found blacker: Just don't get too complicated OR 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.
lib.rs
:
This crate provides the TypestateBuilder
derive macro for generating a
typestate-pattern builder for structs.
This is the helper crate of typestate-builder.
Dependencies
~2.8–4MB
~69K SLoC