In this lesson, you will learn how to leverage Rust's type system to make invalid state transitions physically impossible to express in your code.
The Problem: Illegal States
In traditional programming, we often track state using an enum or a boolean flag inside a struct. For example, a Document might have a status field. The risk here is "state-dependent logic": you might accidentally try to publish() a document that is still in Draft mode. If the check isn't explicitly handled, you face a runtime error.
The Solution: Type-Driven Transitions
Instead of changing a variable's value, we change the type of the object. We define zero-sized types (ZSTs) to represent each state and use a generic struct to hold the state. By defining methods only on specific generic instantiations, we move errors from runtime to compile-time.
Consider this model for a document:
struct Draft;
struct Published;
struct Document<State> {
content: String,
state: State,
}
impl Document<Draft> {
fn new(content: String) -> Self {
Self { content, state: Draft }
}
fn publish(self) -> Document<Published> {
Document {
content: self.content,
state: Published,
}
}
}
Why This Works
Notice that the publish method consumes self (the Document<Draft>) and returns a new Document<Published>. Because the publish method is defined specifically for Document<Draft>, you literally cannot call .publish() on a Document<Published>.
If you tried to write code that attempted to publish an already published document, the Rust compiler would throw an error: no method named publish found for struct Document<Published>. You have encoded your state machine directly into the compiler’s understanding of the program. Your business logic becomes a set of "type paths" that the developer must follow to successfully compile the application.
By using the ownership model to "consume" the previous state and "produce" the next, you ensure that it is impossible to be in two states at once or to perform an illegal action on a state that doesn't support it.
Key Takeaway: By binding methods to specific types representing states, you transform logical runtime bugs into compile-time errors.