Pure guards, effectful actions, zero runtime overhead
Links: GitHub | Crates.io | Documentation
Overview#
Mindset provides a flexible and type-safe state machine implementation that separates pure guard logic from effectful actions. Built on Stillwater’s effect system, it enables you to write state machines that are zero-cost by default, explicitly effectful when needed, and highly testable through the environment pattern.
The Problem#
State machines are fundamental to many systems, but traditional implementations have tradeoffs:
- Runtime overhead - Many state machine libraries impose allocations, dynamic dispatch, or complex runtime machinery
- Hidden side effects - I/O and mutations scattered throughout transition logic make testing difficult
- Tight coupling - Hard-coded dependencies prevent unit testing and reuse
- Lost work - Long-running workflows lose progress when interrupted
- All-or-nothing effects - No way to write pure state machines without carrying effect overhead
The Solution#
Mindset provides zero-cost state machines with explicit, optional effects:
use mindset::{StateMachine, State};
use mindset::builder::{StateMachineBuilder, TransitionBuilder};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ConnectionState {
Disconnected,
Connecting,
Connected,
}
impl State for ConnectionState {}
fn main() {
let machine = StateMachineBuilder::new()
.initial(ConnectionState::Disconnected)
.add_transition(
TransitionBuilder::new()
.from(ConnectionState::Disconnected)
.to(ConnectionState::Connecting)
.succeeds()
.build()
.unwrap()
)
.add_transition(
TransitionBuilder::new()
.from(ConnectionState::Connecting)
.to(ConnectionState::Connected)
.succeeds()
.build()
.unwrap()
)
.build()
.unwrap();
// Zero-cost transitions - compiles to direct state updates
}
Key Features#
Pure Guards, Effectful Actions#
Separate deterministic logic from side effects:
// Pure guard - no side effects
fn can_submit(order: &Order) -> bool {
order.total > 0.0
}
// Effectful action - explicit environment usage
fn submit_order<Env>(order: &mut Order, env: &mut Env) -> Result<(), String>
where
Env: PaymentProcessor + Logger,
{
env.log(&format!("Submitting order {}", order.id));
env.charge(order.total)?;
Ok(())
}
Environment Pattern#
Clean dependency injection through trait bounds:
trait PaymentProcessor {
fn charge(&mut self, amount: f64) -> Result<(), String>;
}
trait Logger {
fn log(&mut self, message: &str);
}
// Compose environments from multiple traits
fn process<Env>(env: &mut Env) -> Result<(), String>
where
Env: PaymentProcessor + Logger,
{
env.log("Processing payment");
env.charge(99.99)?;
Ok(())
}
Zero-Cost Abstractions#
Pay only for what you use:
// No effects = zero overhead (compiles to direct assignment)
machine.transition(State::A, State::B);
// With effects = only pay for actual I/O
machine.transition_with_effect(State::A, State::B, |state, env| {
env.log("Transitioning");
env.save_to_db(state)?;
Ok(())
});
Checkpoint and Resume#
Built-in persistence for long-running workflows:
// Workflow automatically saves checkpoints
./workflow run --config workflow.yaml
// Resume after interruption
./workflow resume --checkpoint ./checkpoints/latest.json
// Continues from where it left off
Features:
- Automatic checkpointing at key phases
- JSON (human-readable) and binary (compact) formats
- Atomic writes prevent corruption
- Resume seamlessly after crashes or planned stops
Technical Highlights#
- Language: Rust
- Foundation: Built on Stillwater’s
Effecttype for controlled side effects - Core Types:
StateMachine,State,Transition,Guard,Action - Performance: Zero-cost pure transitions, monomorphized effect dispatch
- Pattern: Functional core (pure guards), imperative shell (effectful actions)
- Testing: Environment pattern enables easy mocking
Installation#
# Add to Cargo.toml
cargo add mindset
# Or manually
[dependencies]
mindset = "0.1"
Quick Examples#
Traffic Light (Zero Cost)#
use mindset::state_enum;
use mindset::builder::{StateMachineBuilder, simple_transition};
state_enum! {
enum TrafficLight {
Red,
Yellow,
Green,
}
}
let machine = StateMachineBuilder::new()
.initial(TrafficLight::Red)
.transitions(vec![
simple_transition(TrafficLight::Red, TrafficLight::Green),
simple_transition(TrafficLight::Green, TrafficLight::Yellow),
simple_transition(TrafficLight::Yellow, TrafficLight::Red),
])
.build()
.unwrap();
Order Processing (With Effects)#
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum OrderState {
Draft,
Submitted,
Processing,
Completed,
}
impl State for OrderState {}
struct Order {
id: u64,
total: f64,
}
trait PaymentProcessor {
fn charge(&mut self, amount: f64) -> Result<(), String>;
}
fn submit_order<Env>(order: &mut Order, env: &mut Env) -> Result<(), String>
where
Env: PaymentProcessor,
{
env.charge(order.total)?;
Ok(())
}
Testing with Mocks#
struct MockEnv {
logged: Vec<String>,
saved: Vec<String>,
}
impl Logger for MockEnv {
fn log(&mut self, msg: &str) {
self.logged.push(msg.to_string());
}
}
impl Database for MockEnv {
fn save(&mut self, data: &str) -> Result<(), String> {
self.saved.push(data.to_string());
Ok(())
}
}
#[test]
fn test_transition() {
let mut env = MockEnv {
logged: vec![],
saved: vec![],
};
transition(&mut state, &mut env).unwrap();
assert_eq!(env.logged.len(), 1);
assert_eq!(env.saved.len(), 1);
}
Performance#
Zero-Cost Pure Transitions#
Pure transitions have zero runtime overhead:
machine.transition(State::A, State::B);
Compiles to:
state = State::B;
Effect Cost Model#
- No effects: Zero overhead (direct state update)
- Single trait: Monomorphized static dispatch (zero-cost abstraction)
- Multiple traits: One vtable lookup per trait (if using trait objects)
- Environment mutation: Direct field access
Typical timings on modern CPU:
- Pure transition: ~0.5ns
- Single-effect transition: ~2-3ns
- Multi-effect transition: ~5-10ns
Use Cases#
Mindset is ideal for:
- Workflow engines - Document approval, order processing, account lifecycle
- Protocol implementations - Connection state, handshake sequences
- Resource management - File handles, database connections, network sockets
- Long-running processes - MapReduce workflows, batch processing with checkpoints
- Game logic - Character states, AI behavior trees
- Business processes - Application state, multi-step forms
Design Philosophy#
Built on functional programming principles from Stillwater:
Functional Core, Imperative Shell
- Pure guards for business logic
- Effectful actions at boundaries
- Clear separation enables testing
Pay Only for What You Use
- Zero-cost by default
- Effects are opt-in
- No hidden overhead
Explicit Over Implicit
- Side effects visible in signatures
- Environment dependencies via trait bounds
- Clear data flow
Testability First
- Pure functions trivial to test
- Mock environments for integration tests
- Clear dependency injection
Documentation#
- Builder Guide: Comprehensive guide to the builder API
- Checkpointing Guide: Checkpoint and resume patterns
- Effects Guide: Effect patterns and best practices
- API Documentation: Generated API docs
Project Status#
Active development, available on crates.io. Implements core specifications:
- Spec 001: Core state machine with pure guards
- Spec 002: Effect-based transitions with environment pattern
- Spec 004: Checkpoint and resume for persistence
- Spec 005: Builder API for ergonomic construction
Links#
- GitHub: github.com/iepathos/mindset
- Documentation: docs.rs/mindset
- Crates.io: Available via
cargo add mindset