Expand description
Compile-time verified typestate workflows for Rust.
Statum is for workflow and protocol models where representational
correctness matters. It helps keep invalid, undesirable, or not-yet-
validated states out of ordinary code.
In the same spirit as Option and Result, it uses the type system to
make absence, failure, and workflow legality explicit instead of leaving
them in status fields and guard code. 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::SomeState::InReview(task) => assert_eq!(task.state_data, "alice"),
_ => panic!("expected in-review task"),
}
Ok(())
}If you want explainable rebuild traces, validators can also return
Validation. Then .build_report() and .build_reports() populate RebuildAttempt::reason_key and
RebuildAttempt::message for failed matches while keeping the normal
.into_result() surface.
§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>§Machine Introspection
Statum can also expose the static machine structure as typed metadata. This is useful when the same machine definition should drive:
- CLI explainers
- generated docs
- graph exports
- exact transition assertions in tests
- runtime replay or debug tooling
The important detail is that the graph is exact at the transition-site level. A consumer can ask for the legal targets of one specific method on one specific source state.
The graph is derived from macro-expanded, cfg-pruned #[transition]
method signatures. Supported return shapes are direct machine returns plus
Option, Result, and statum::Branch wrappers around machine types.
Unsupported custom decision enums are rejected instead of approximated.
For small amounts of human-facing metadata, Statum can also generate a
machine::PRESENTATION constant from #[present(...)] attributes. Add
#[presentation_types(...)] on the machine when those attributes should
carry typed metadata = ... payloads instead of just labels and
descriptions.
use statum::{
machine, state, transition, MachineIntrospection, MachineTransitionRecorder,
};
#[state]
enum FlowState {
Fetched,
Accepted,
Rejected,
}
#[machine]
struct Flow<FlowState> {}
#[transition]
impl Flow<Fetched> {
fn validate(self, accept: bool) -> Result<Flow<Accepted>, Flow<Rejected>> {
if accept {
Ok(self.accept())
} else {
Err(self.reject())
}
}
fn accept(self) -> Flow<Accepted> {
self.transition()
}
fn reject(self) -> Flow<Rejected> {
self.transition()
}
}
fn main() {
let graph = <Flow<Fetched> as MachineIntrospection>::GRAPH;
let validate = graph
.transition_from_method(flow::StateId::Fetched, "validate")
.unwrap();
assert_eq!(
graph.legal_targets(validate.id).unwrap(),
&[flow::StateId::Accepted, flow::StateId::Rejected]
);
let event = <Flow<Fetched> as MachineTransitionRecorder>::try_record_transition_to::<
Flow<Accepted>,
>(Flow::<Fetched>::VALIDATE)
.unwrap();
assert_eq!(event.chosen, flow::StateId::Accepted);
}Transition ids are exact and typed, but they are exposed as generated
associated consts on the source-state machine type, such as
Flow::<Fetched>::VALIDATE.
§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.
Structs§
- Machine
Descriptor - Rust-facing identity for a machine family.
- Machine
Graph - Structural machine graph emitted from macro-generated metadata.
- Machine
Presentation - Optional human-facing metadata layered on top of a machine graph.
- Machine
Presentation Descriptor - Optional machine-level presentation metadata.
- Rebuild
Attempt - One validator evaluation recorded during typed rehydration.
- Rebuild
Report - A typed rehydration result plus the validator attempts that produced it.
- Recorded
Transition - A runtime record of one chosen transition.
- Rejection
- A structured validator rejection captured during typed rehydration.
- State
Descriptor - Static descriptor for one generated state id.
- State
Presentation - Optional state-level presentation metadata.
- Transition
Descriptor - Static descriptor for one transition site.
- Transition
Inventory - Runtime accessor for transition descriptors that may be supplied by a distributed registration surface.
- Transition
Presentation - Optional transition-level presentation metadata.
- Transition
Presentation Inventory - Runtime accessor for transition presentation metadata that may be supplied by a distributed registration surface.
Enums§
- Branch
- A first-class two-way branching transition result.
- 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.
- Machine
Introspection - Static introspection surface emitted for a generated Statum machine.
- Machine
State Identity - Identity for one concrete machine state.
- Machine
Transition Recorder - Runtime recording helpers layered on top of static machine introspection.
- 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.
- Validation
- An opt-in validator result that carries structured rejection details.
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.