Expand description
Compile-time verified typestate surfaces for Rust.
Statum is for values whose phase should change what methods are legally
available on that value. 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
Use Statum when pressing . before and after a phase change should show a
meaningfully different method surface.
Durable workflows and protocols are one strong fit. Staged validation, resolution, and build surfaces are another. The current macro surface is machine-shaped:
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 = Task::rebuild(&row).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
With the strict-introspection feature enabled, 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 and treat that metadata as the
authoritative static graph surface.
Strict introspection is derived from locally readable #[transition]
method signatures plus any explicit #[introspect(return = ...)] escape
hatches. Supported return shapes are direct machine returns plus canonical
wrapper paths around machine types:
::core::option::Option<Machine<NextState>>,
::core::result::Result<Machine<NextState>, E>, and
::statum::Branch<Machine<Left>, Machine<Right>>.
Unsupported custom decision enums, wrapper aliases, and differently-qualified
machine paths are rejected instead of approximated. In the default feature
set, Statum still follows some source-backed aliases for ergonomics, but
that mode should be treated as convenient metadata rather than the strongest
exactness guarantee. Whole-item #[cfg] gates are supported, but nested
#[cfg] or #[cfg_attr] on #[state] variants, variant payload fields,
or #[machine] fields are rejected because they would otherwise drift the
generated metadata from the active build.
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,
) -> ::core::result::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.