Expand description
Compile-time verified typestate workflows for Rust.
Statum is for workflows where phase order matters and invalid transitions are expensive. It generates typed state markers, typed machines, transition helpers, and typed rehydration from stored data.
§Mental Model
statedefines the legal phases.machinedefines the durable context carried across phases.transitiondefines the legal edges between phases.validatorsrebuilds typed machines from persisted data.
§Quick Start
use statum::{machine, state, transition};
#[state]
enum CheckoutState {
EmptyCart,
ReadyToPay(OrderDraft),
Paid,
}
#[derive(Clone)]
struct OrderDraft {
total_cents: u64,
}
#[machine]
struct Checkout<CheckoutState> {
id: String,
}
#[transition]
impl Checkout<EmptyCart> {
fn review(self, total_cents: u64) -> Checkout<ReadyToPay> {
self.transition_with(OrderDraft { total_cents })
}
}
#[transition]
impl Checkout<ReadyToPay> {
fn pay(self) -> Checkout<Paid> {
self.transition()
}
}
fn main() {
let cart = Checkout::<EmptyCart>::builder()
.id("order-1".to_owned())
.build();
let ready = cart.review(4200);
assert_eq!(ready.state_data.total_cents, 4200);
let _paid = ready.pay();
}§Typed Rehydration
#[validators] lets you rebuild persisted rows back into typed machine
states:
use statum::{machine, state, validators, Error};
#[state]
enum TaskState {
Draft,
InReview(String),
Published,
}
#[machine]
struct Task<TaskState> {
id: u64,
}
struct TaskRow {
id: u64,
status: &'static str,
reviewer: Option<String>,
}
#[validators(Task)]
impl TaskRow {
fn is_draft(&self) -> statum::Result<()> {
if self.status == "draft" {
Ok(())
} else {
Err(Error::InvalidState)
}
}
fn is_in_review(&self) -> statum::Result<String> {
if self.status == "in_review" {
self.reviewer.clone().ok_or(Error::InvalidState)
} else {
Err(Error::InvalidState)
}
}
fn is_published(&self) -> statum::Result<()> {
if self.status == "published" {
Ok(())
} else {
Err(Error::InvalidState)
}
}
}
fn main() -> statum::Result<()> {
let row = TaskRow {
id: 7,
status: "in_review",
reviewer: Some("alice".to_owned()),
};
let row_id = row.id;
let machine = row.into_machine().id(row_id).build()?;
match machine {
task::State::InReview(task) => assert_eq!(task.state_data, "alice"),
_ => panic!("expected in-review task"),
}
Ok(())
}§Compile-Time Gating
Methods only exist on states where you define them.
ⓘ
use statum::{machine, state};
#[state]
enum LightState {
Off,
On,
}
#[machine]
struct Light<LightState> {}
let light = Light::<Off>::builder().build();
let _ = light.switch_off(); // no such method on Light<Off>§Where To Look Next
- Start with
state,machine, andtransition. - For stored rows and database rebuilds, read
validators. - For append-only event logs, use
projectionbefore validator rebuilds. - The repository README and
docs/directory contain longer guides and showcase applications.
Modules§
- projection
- Event-stream projection helpers for Statum rebuild flows.
Enums§
- Error
- Errors returned by Statum runtime helpers.
Traits§
- CanTransition
Map - A machine that can transition by mapping its current state data into
Next. - CanTransition
To - A machine that can transition directly to
Next. - CanTransition
With - A machine that can transition using
Data. - Data
State - A generated state marker that carries payload data.
- State
Marker - A generated state marker type.
- Unit
State - A generated state marker with no payload.
Type Aliases§
- Result
- Convenience result alias used by Statum APIs.
Attribute Macros§
- machine
- Define a typed machine that carries durable context across states.
- state
- Define the legal lifecycle phases for a machine.
- transition
- Validate and generate legal transitions for one source state.
- validators
- Rebuild typed machines from persisted data.