You can implement all the functions on a single struct using phantom type parameters. The Customer struct holds some state:
pub struct Customer<State> {
state: PhantomData<State>,
}
We can create the possible states that a Customer can be in:
pub struct CustomerStateNew;
pub struct CustomerStateValidated;
pub struct CustomerStateWithCode;
When you create a Customer, it's state is CustomerStateNew:
pub fn add_customer() -> Customer<CustomerStateNew> {
Customer { state: PhantomData }
}
To validate a Customer it must be in the CustomerStateNew state:
impl Customer<CustomerStateNew> {
pub fn validate(&self) -> Customer<CustomerStateValidated> {
Customer { state: PhantomData }
}
}
The Customer must be validated (CustomerStateValidated) to generate a code:
impl Customer<CustomerStateValidated> {
pub fn generate_code(&self) -> Customer<CustomerStateWithCode> {
Customer { state: PhantomData }
}
}
And it must have a generated code (CustomerStateWithCode) to be created. create consumes self, so the customer cannot be used after it is created (you might not want this behavior, but I included it here for completeness):
impl Customer<CustomerStateWithCode> {
pub fn create(self) -> Result<(), ()> {
Ok(())
}
}
Now we can chain together the methods to create the user:
let result = add_customer().validate().generate_code().create()?;
However, it we try to create the Customer before it is validated, the code will not compile:
let result = add_customer().create();
// error[E0599]: no method named `create` found for struct `Customer<CustomerStateNew>`
// --> src/main.rs:36:20
// 36 | add_customer().create();
// | ^^^^^^ method not found in `Customer<CustomerStateNew>`
Also, no one else can create a Customer with an arbitrary state, because the state field is private:
mod somewhere_else {
fn bla() {
let customer: Customer<CustomerStateWithCode> = Customer { state: PhantomData };
customer.create();
}
}
// error[E0451]: field `state` of struct `Customer` is private
// --> src/main.rs:41:64
// |
// 41 | let customer: Customer<CustomerStateWithCode> = Customer { state: PhantomData };
// |
If you want to store data specific to each state, you can store that actual State inside the Customer instead of PhantomData. Now however, the state is more than just compile time safety and will be stored at runtime:
pub struct CustomerStateWithCode(pub usize);
pub struct Customer<State> {
state: State,
}
impl Customer<CustomerStateValidated> {
pub fn generate_code(&self) -> Customer<CustomerStateWithCode> {
Customer { state: CustomerStateWithCode(1234) }
}
}
We have created a simple state machine using phantom types. This is also knows as the type state pattern. Note that the states will be compiled away to nothing, so there is no runtime cost, only compile time safety!
Playground link