Statum: Zero-Boilerplate Compile-Time State Machines in Rust

github.com

23 points by eboody 3 days ago


bfrog - a day ago

One of the issue with type states if you aren’t aware is the state of each type needs to then be a type parameter of the parent type. Meaning creating a dynamic number of these state machines would be frustrating.

Do you have some thoughts or samples of say a Vec of Lights as an extension of the sample tou have?

eboody - 3 days ago

Hey Rustaceans! I've created a library called *Statum* for building type-safe state machines in Rust. It ensures invalid state transitions are caught at *compile time*, giving you extra safety with minimal boilerplate.

---

## Quick Example (Minimal)

```rust use statum::{state, machine};

#[state] pub enum TaskState { New, InProgress, Complete, }

#[machine] struct Task<S: TaskState> { id: String, name: String, }

impl Task<New> { fn start(self) -> Task<InProgress> { self.transition() } }

impl Task<InProgress> { fn complete(self) -> Task<Complete> { self.transition() } }

fn main() { let task = Task::new("task-1".to_owned(), "Important Task".to_owned()) .start() .complete(); } ```

### How it works - *`#[state]`* transforms your enum variants into separate structs while generating a trait to represent the states. - *`#[machine]`* injects some extra fields to track which state your machine is in, at the type level.

---

## States with Data

Need states to carry extra information?

```rust #[state] pub enum DocumentState { Draft, Review(ReviewData), // State with associated data Published, }

struct ReviewData { reviewer: String, comments: Vec<String>, }

impl Document<Draft> { fn submit_for_review(self, reviewer: String) -> Document<Review> { // Transition to `Review` with data: self.transition_with(ReviewData { reviewer, comments: vec![], }) } } ```

### Accessing State Data

When your state has associated data, use `.get_state_data()` or `.get_state_data_mut()`:

```rust impl Document<Review> { fn add_comment(&mut self, comment: String) { if let Some(review_data) = self.get_state_data_mut() { review_data.comments.push(comment); } }

    fn get_reviewer(&self) -> Option<&str> {
        self.get_state_data().map(|data| data.reviewer.as_str())
    }

    fn approve(self) -> Document<Published> {
        self.transition()
    }
} ```

---

## Why Statum?

Many Rust state machine libraries need: - Complex trait impls - Manual checks for transitions - Runtime guards for state data

*Statum* does all of this at compile time with a straightforward API.

---

## Gotchas: Attribute Ordering - *`#[machine]`* must come before any user `#[derive(...)]` on the same struct, or you may see: ``` error[E0063]: missing fields `marker` and `state_data` ``` This is because the derive macros expand before Statum can inject its hidden fields.

---

## Feedback Welcome!

I'm actively developing Statum and would love community input: - Which patterns do you commonly use for state machines? - Features you’d like to see added? - Ideas to make the API smoother?

---

## Installation

```bash cargo add statum ```

Check it out on [GitHub](https://github.com/eboody/statum) or [Crates.io](https://crates.io/crates/statum). Let me know what you think!