The State pattern is related to a finite-state machine (FSM) concept, however, instead of implementing a lot of conditional statements, each state is represented by a separate type that implements a common state trait.
Transitions between states depend on the particular trait implementation for each state type.
The State Pattern in Rust is described in detail in The Rust Book: https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html
cargo run --bin statePress buttons, ESC for exit, enjoy!
![]() |
![]() |
Let's build a music player with the following state transitions:
There is a base trait State with play and stop methods which make state
transitions:
pub trait State {
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State>;
fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State>;
}next and prev don't change state, there are default implementations
in a separate impl dyn State block that cannot be overridden.
impl dyn State {
pub fn next(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
self
}
pub fn prev(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
self
}
}Every state is a type implementing the trait State:
pub struct StoppedState;
pub struct PausedState;
pub struct PlayingState;
impl State for StoppedState {
...
}
impl State for PausedState {
...
}Anyways, it works as follows:
let state = Box::new(StoppedState); // StoppedState.
let state = state.play(&mut player); // StoppedState -> PlayingState.
let state = state.play(&mut player); // PlayingState -> PausedState.Here, the same action play makes a transition to different states depending
on where it's called from:
-
StoppedState's implementation ofplaystarts playback and returnsPlayingState.fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> { player.play(); // Stopped -> Playing. Box::new(PlayingState) }
-
PlayingStatepauses playback after hitting the "play" button again:fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> { player.pause(); // Playing -> Paused. Box::new(PausedState) }
💡 The methods are defined with a special self: Box<Self> notation.
Why is that?
- First,
selfis not a reference, it means that the method is a "one shot", it consumesselfand exchanges onto another state returningBox<dyn State>. - Second, the method consumes the boxed object like
Box<dyn State>and not an object of a concrete type likePlayingState, because the concrete state is unknown at compile time.


