0 day streak
rust / expert
Snippet

Enforcing State Machine Invariants at Compile-Time using PhantomData

The Type-State Pattern leverages Rust's type system to enforce valid transitions in a state machine at compile-time. By utilizing generic parameters representing the state and PhantomData to satisfy the compiler without runtime overhead, we prevent invalid operations (such as viewing an unpublished post) from even compiling.

snippet.rs
rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::marker::PhantomData;
pub struct Draft;pub struct Published;
pub struct Post<State> {
content: String,
_state: PhantomData<State>,
}
impl Post<Draft> {
pub fn new(content: String) -> Self {
Post { content, _state: PhantomData }
}
pub fn publish(self) -> Post<Published> {
Post { content: self.content, _state: PhantomData }
}
}
impl Post<Published> {
pub fn view(&self) -> &str {
&self.content
}
}
Breakdown
1
pub struct Post<State> {
Declares a generic struct where State serves as a type-level marker representing the current lifecycle stage of the post.
2
_state: PhantomData<State>,
Instructs the compiler that Post acts as if it owns a value of type State, preventing unused type parameter errors without allocating runtime memory.
3
impl Post<Draft> {
Implements methods that are exclusively available when the Post is in the Draft state.
4
pub fn publish(self) -> Post<Published> {
Consumes the draft post and returns a new post marked with the Published state, preventing further state mutation.