Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

State Pattern with Rust Enums

In the official Rust book, there's a section that attempts to provide an example of the State design pattern in order to showcase some of Rust's OOP muscles. If you are not familiar with the state pattern, I suggest reading up…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

In the official Rust book, there's a section that attempts to provide an example of the State design pattern in order to showcase some of Rust's OOP muscles.






If you are not familiar with the state pattern, I suggest reading up on it before continuing.



I have no fight to pick with OOP design patterns, but as I read this example, I found its design to be odd. I started wondering why the example wasn't taking advantage of enums. Then like magic the book included this figure text:




You may have been wondering why we didn’t use an enum with the different possible post states as variants. That’s certainly a possible solution, try it and compare the end results to see which you prefer! One disadvantage of using an enum is every place that checks the value of the enum will need a match expression or similar to handle every possible variant. This could get more repetitive than this trait object solution.




The Rust Book



After reading this I still disagreed that structs were the better choice. So I decided to take the challenge to build my own state machine using Rust's enums.



Before we get started, let's first look at an example using structs.






A State Machine with Structs



The scenario being covered in the Rust book involved the different states of an article post. However, I came across a similar example from none other than Refactoring Guru which involves a music player.



The music player will have four buttons:




  • Play

  • Stop

  • Prev Track

  • Next Track



Some of these buttons (Play and Stop) should behave differently depending on the current state of the music player. While others (Prev/Next Track) should behave the same regardless of state.






The State Struct



Refactoring Guru's example follows a similar strategy as the Rust Book, by creating a collection of State structs, each one handling their own behavior for each button of the music player. The state struct also has a mutable reference to the music player so it can apply the necessary side effect for each action.



*A lot of these code snippets will be edited for the sake of brevity, but I will include a link to the full code snippet provided by Refactoring Guru.



Ref: https://refactoring.guru/design-patterns/state/rust/example#example-0--state-rs




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>;
}

impl State for StoppedState {
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Play/Pause" button for the "Stopped" state.
}

fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Stop" button for the "Stopped" state.
}
}

impl State for PausedState {
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Play/Pause" button for the "Paused" state.
}

fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Stop" button for the "Paused" state.
}
}

impl State for PlayingState {
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Play/Pause" button for the "Playing" state.
}

fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
// Apply logic for the "Stop" button for the "Playing" state.
}
}






This code is pretty straightforward in what its trying to achieve. However it does feel a bit more cluttered given the duplicate function declarations, along with the added boilerplate for dynamic dispatch.



If you're not familiar with Rust's dynamic dispatch feature, (the bits about Box<dyn T>) it's not required for reading this article. But it is an important concept I would recommend to those wanting to learn Rust.



Lastly, this strategy puts more reliance on the developers' cognitive ability to remember all possible states and buttons. This may seem like a silly excuse regarding a music player, but it can be particularly challenging if you require a more complex state machine.






Actions as Strings



One thing I like about Refactoring Guru's example is their incorporation of an interactive UI which you can test your state machine on. However, when handling UI events, the example relies on static strings to define the trigger action.



Ref: https://doc.rust-lang.org/stable/book/ch17-02-trait-objects.html#trait-objects-perform-dynamic-dispatch




let mut app = cursive::default();

// ...

app.add_layer(
Dialog::around(TextView::new("Press Play").with_name("Player Status"))
.title("Music Player")
.button("Play", |s| execute(s, "Play"))
.button("Stop", |s| execute(s, "Stop"))
.button("Prev", |s| execute(s, "Prev"))
.button("Next", |s| execute(s, "Next")),
);

// ...

fn execute(s: &mut Cursive, button: &'static str) {
let PlayerApplication {
mut player,
mut state,
} = s.take_user_data().unwrap();

let mut view = s.find_name::<TextView>("Player Status").unwrap();

state = match button {
"Play" => state.play(&mut player),
"Stop" => state.stop(&mut player),
"Prev" => state.prev(&mut player),
"Next" => state.next(&mut player),
_ => unreachable!(),
};
}






By using static strings, we're again trusting the developers to remember all possible values when checking which button was pressed.






Bring Out the Enums Already



Now comes my attempt to refactor this using Rust's enums. The first thing I did was define enum itself, which is pretty straightforward.




pub enum PlayerState {
Stopped,
Playing,
Paused,
}






Now the question is where to put this state. The original example puts the state inside the application struct. However I'm not a fan of this as it limits how we can extend our app to implement other features that may require their own state.



Because of this, I put the state enum inside the Player struct.




pub struct Player {
pub state: PlayerState,
playlist: Vec<Track>,
current_track: usize,
_volume: u8,
}






When I implement the default method for the Player struct, I set the initial state as Stopped.




impl Default for Player {
fn default() -> Self {
Self {
state: PlayerState::Stopped,
playlist: vec![...],
current_track: 0,
_volume: 25,
}
}
}






The rest of the Player struct implements the same methods as the original example, next_track(), prev_track(), play(), pause() and so on. This also means none of these methods rely on our state enum. They only hold the logic for what the Player should do when they're invoked.






The main function



While I'll keep using the cursive crate for the apps UI, I decided to move its rendering logic out of the State struct and into a new struct called Renderer.



I'll first update the app definition to instantiate our music player struct, but we no longer maintain state here.




#[derive(Default)]
struct App {
player: Player,
renderer: Renderer,
}






You may have noticed a Renderer struct in this snippet, which I will get to its explanation later.



Next, we define our main function which looks similar to the original example, except again we're using enums to define the transition that should occur on each button press.




enum Transition {
Play,
Stop,
Prev,
Next,
}

fn main() {
let mut app = cursive::default();

app.set_user_data(App::default());
app.add_layer(
Dialog::around(TextView::new("Press Play").with_name("Player Status"))
.title("Music Player")
.button("Play", |s| execute(s, Transition::Play))
.button("Stop", |s| execute(s, Transition::Stop))
.button("Prev", |s| execute(s, Transition::Prev))
.button("Next", |s| execute(s, Transition::Next)),
);

app.add_global_callback(Key::Esc, |s| s.quit());

app.run();
}






Now we get to the fun part, the state machine logic that will be contained inside execute(). The function starts off the same as the original example with the one addition of renderer which I promise I will get to.




fn execute(s: &mut Cursive, action: Transition) {
let App {
mut player,
renderer,
} = s.take_user_data().unwrap();

let mut view = s.find_name::<TextView>("Player Status").unwrap();






Next, we're going to be doing a lot of referencing to both of our enums in coming lines, so let's create a shorthand to keep things easy on the eyes.




  use PlayerState as S;
use Transition as T;






Finally, we come to our match expression. What we want to do is match the current state of the music player and the transition that will occur due to the button press. To make the comparisons easier, let's put both the state and transition value in a tuple.




  match (&player.state, action) {
(S::Playing, T::Play) => {
player.pause();
player.state = S::Paused;
}
(_, T::Play) => {
player.play();
player.state = S::Playing;
}
(S::Stopped, T::Stop) => (),
(_, T::Stop) => {
player.pause();
player.rewind();
player.state = S::Stopped;
}
(_, T::Next) => player.next_track(),
(_, T::Prev) => player.prev_track(),
}






I tried to order this list by the transition T, covering T::Play first, then T::Stop with T::Next and T::Prev to end it.






What I Like About This Strategy



By using enums in our match expression we gain a significant advantage compared to the struct example, in that the Rust compiler now takes responsibility for ensuring every condition is met for all unique combinations of states and transitions.



Additionally, we get a small performance improvement since we no longer rely on dynamic dispatch to pass arguments the implement a State trait.



Revisiting the caution about enums from the Rust Book:




One disadvantage of using an enum is every place that checks the value of the enum will need a match expression or similar to handle every possible variant. This could get more repetitive than this trait object solution.




I would argue this code is far less repetitive than defining the same struct methods for every single state.



Plus, there will be many cases where a transition only requires a special behavior in one or two states and does nothing for all remaining states. When using enums, the underscore declaration becomes useful to group together any remaining states that should share the same behavior.



Lastly, if we were working on a more complex state machine, we could extract each transition arm into its own function (or module if needed) to handle the behavior of each state for that specific transition.






What About the Renderer struct



As promised, I will now whare what the Renderer struct does.



Originally the code has the State struct apply any UI side effects. However, I opted to contain any UI updates inside a Renderer struct which we can invoke as a side effect of our state changes.



It too can take advantage of the enum match expression to ensure all cases are covered when dealing with UI updates.




#[derive(Default)]
struct Renderer {}

impl Renderer {
pub fn update(&self, player: &Player, view: &mut TextView) {
match player.state {
PlayerState::Stopped => view.set_content("[Stopped] Press 'Play'"),
PlayerState::Playing => view.set_content(format!(
"[Playing] {} - {} sec",
player.track().title,
player.track().duration
)),
PlayerState::Paused => view.set_content(format!(
"[Paused] {} - {} sec",
player.track().title,
player.track().duration
)),
}
}
}






I like this approach for two reasons:




  1. It separates the concerns of how the music player and the UI should behave depending on the current state.

  2. It removes the last reason for us to even have a state struct.






Conclusion



I by no means have any beef with the author(s) of the Rust book or Refactoring Guru. The examples they provide still teach valuable strategies that shouldn't be ignored.



This challenge was enjoyable, and I hope it helps others to come up with their own original solutions using the full capabilities of Rust.






Full Code Example



player.rs




pub struct Track {
pub title: String,
pub duration: u32,
cursor: u8,
}

impl Track {
pub fn new(title: &str, duration: u32) -> Self {
Self {
title: title.into(),
duration,
cursor: 0,
}
}
}

pub enum PlayerState {
Stopped,
Playing,
Paused,
}

pub struct Player {
pub state: PlayerState,
playlist: Vec<Track>,
current_track: usize,
_volume: u8,
}

impl Default for Player {
fn default() -> Self {
Self {
state: PlayerState::Stopped,
playlist: vec![
Track::new("Track 1", 180),
Track::new("Track 2", 250),
Track::new("Track 3", 130),
Track::new("Track 4", 220),
Track::new("Track 5", 300),
],
current_track: 0,
_volume: 25,
}
}
}

impl Player {
pub fn next_track(&mut self) {
self.current_track = (self.current_track + 1) % self.playlist.len();
}
pub fn prev_track(&mut self) {
self.current_track = (self.playlist.len() + self.current_track - 1) % self.playlist.len();
}

pub fn play(&mut self) {
self.track_mut().cursor = 10; // Playback imitation.
}

pub fn pause(&mut self) {
self.track_mut().cursor = 43; // Paused at some moment.
}

pub fn rewind(&mut self) {
self.track_mut().cursor = 0;
}

pub fn track(&self) -> &Track {
&self.playlist[self.current_track]
}

fn track_mut(&mut self) -> &mut Track {
&mut self.playlist[self.current_track]
}
}






main.rs




mod player;
use crate::player::{Player, PlayerState};
use cursive::{
event::Key,
view::Nameable,
views::{Dialog, TextView},
Cursive,
};

enum Transition {
Play,
Stop,
Prev,
Next,
}

#[derive(Default)]
struct Renderer {}

impl Renderer {
pub fn update(&self, player: &Player, view: &mut TextView) {
match player.state {
PlayerState::Stopped => view.set_content("[Stopped] Press 'Play'"),
PlayerState::Playing => view.set_content(format!(
"[Playing] {} - {} sec",
player.track().title,
player.track().duration
)),
PlayerState::Paused => view.set_content(format!(
"[Paused] {} - {} sec",
player.track().title,
player.track().duration
)),
}
}
}

#[derive(Default)]
struct App {
player: Player,
renderer: Renderer,
}

fn main() {
let mut app = cursive::default();

app.set_user_data(App::default());
app.add_layer(
Dialog::around(TextView::new("Press Play").with_name("Player Status"))
.title("Music Player")
.button("Play", |s| execute(s, Transition::Play))
.button("Stop", |s| execute(s, Transition::Stop))
.button("Prev", |s| execute(s, Transition::Prev))
.button("Next", |s| execute(s, Transition::Next)),
);

app.add_global_callback(Key::Esc, |s| s.quit());

app.run();
}

fn execute(s: &mut Cursive, action: Transition) {
let App {
mut player,
renderer,
} = s.take_user_data().unwrap();

let mut view = s.find_name::<TextView>("Player Status").unwrap();

use PlayerState as S;
use Transition as T;
match (&player.state, action) {
(S::Playing, T::Play) => {
player.pause();
player.state = S::Paused;
}
(_, T::Play) => {
player.play();
player.state = S::Playing;
}
(S::Stopped, T::Stop) => (),
(_, T::Stop) => {
player.pause();
player.rewind();
player.state = S::Stopped;
}
(_, T::Next) => player.next_track(),
(_, T::Prev) => player.prev_track(),
}

renderer.update(&player, &mut view);

s.set_user_data(App { player, renderer });
}


Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten State Pattern with Rust Enums

Thematisch verwandte Begriffe: State, Pattern, with, Rust · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-17636 | IBM Financial Transaction Manager (FTM) for RedHat OpenShift could allow…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick