| .github/workflows | ||
| docs | ||
| examples | ||
| hooks | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| CHANGELOG.md | ||
| config.nims | ||
| CONTRIBUTING.md | ||
| docs-requirements.txt | ||
| LICENSE | ||
| mkdocs.yml | ||
| nim.cfg | ||
| README.md | ||
| typestates.nimble | ||
nim-typestates
Compile-time state machine validation for Nim.
Overview
nim-typestates encodes state machines in Nim's type system. Invalid state transitions become compile-time errors instead of runtime bugs.
import typestates
type
Payment = object
id: string
amount: int
Created = distinct Payment
Authorized = distinct Payment
Captured = distinct Payment
typestate Payment:
states Created, Authorized, Captured
transitions:
Created -> Authorized
Authorized -> Captured
proc authorize(p: Created): Authorized {.transition.} =
result = Authorized(p.Payment)
proc capture(p: Authorized): Captured {.transition.} =
result = Captured(p.Payment)
# Valid usage
let payment = Created(Payment(id: "pay_123", amount: 9999))
let authed = payment.authorize()
let captured = authed.capture()
# Compile error: type mismatch, got 'Created' but expected 'Authorized'
# let bad = payment.capture()
Guarantees
The typestate macro and {.transition.} pragma enforce state machine
rules at compile time. A program that compiles has been verified by the
compiler to contain no invalid state transitions.
Verified at compile time
- Protocol adherence: Operations are only callable in valid states
- Transition validity: All
{.transition.}procs follow declared paths - State exclusivity: Each object occupies exactly one state
Not verified
- Functional correctness: The implementation of each proc
- Specification accuracy: Whether the declared state machine matches the intended real-world protocol
The compiler verifies that your code follows the declared protocol. It does not verify that the protocol itself is correct.
Installation
nimble install typestates
Or add to your .nimble file:
requires "typestates >= 0.1.0"
⚠️ Nim < 2.2.8 with Static Generics
If you use
staticgeneric parameters (e.g.,Buffer[N: static int]) with ARC/ORC/AtomicARC, you may hit a Nim codegen bug fixed in Nim 2.2.8. The library detects this and shows workarounds. Options:
- Use
--mm:refcinstead of ARC/ORC- Make your base type inherit from
RootObjand addinheritsFromRootObj = true- Upgrade to Nim >= 2.2.8 (when released)
- Add
consumeOnTransition = falseto your typestateRegular generics (
Container[T]) are not affected.
Usage
Define states as distinct types
import typestates
type
File = object
path: string
fd: int
Closed = distinct File
Open = distinct File
Declare valid transitions
typestate File:
states Closed, Open
transitions:
Closed -> Open
Open -> Closed
Implement transitions
proc open(f: Closed, path: string): Open {.transition.} =
var file = f.File
file.path = path
file.fd = 1
result = Open(file)
proc close(f: Open): Closed {.transition.} =
result = Closed(f.File)
Mark non-transitions
proc read(f: Open, n: int): string {.notATransition.} =
result = "data"
proc write(f: Open, data: string) {.notATransition.} =
discard
Key Features
| Feature | Description |
|---|---|
| Compile-time validation | Invalid transitions are compilation errors |
| Zero runtime cost | All validation happens at compile time |
| Branching transitions | Open -> (Closed | Error) as Result |
| Wildcard transitions | * -> Closed (any state can transition) |
| Generic typestates | Container[T] with states like Empty[T], Full[T] |
| Cross-type bridges | Transition between different typestates |
| Visualization | Export to GraphViz DOT, PNG, SVG |
| CLI tool | Project-wide verification |
| Strict mode | Require explicit marking of all state operations |
| Sealed typestates | Control external module access |
Cross-Type Bridges
Model resource transformation and protocol handoff between typestates:
import typestates
import ./session
typestate AuthFlow:
states Pending, Authenticated, Failed
transitions:
Pending -> Authenticated
Pending -> Failed
bridges:
Authenticated -> Session.Active
Failed -> Session.Guest
# Bridge implementation
converter toSession(auth: Authenticated): Active {.transition.} =
Active(Session(userId: auth.AuthFlow.userId))
Bridges are validated at compile time and shown in visualization.
CLI Tool
Verify typestate rules across your project:
typestates verify src/
Visualization
Generate state machine diagrams from your code:
# Generate SVG
typestates dot src/ | dot -Tsvg -o states.svg
# Generate PNG
typestates dot src/ | dot -Tpng -o states.png
# Minimal output for custom styling
typestates dot --no-style src/ > states.dot
Documentation
- Getting Started
- DSL Reference
- Cross-Type Bridges
- Generic Typestates
- Formal Guarantees
- Error Handling
- Examples
- Strict Mode
- Verification
- CLI Reference
- Visualization
- API Reference
- Contributing
References
- Typestate Pattern in Rust
- typestate crate for Rust
- Plaid Language
- Typestate: A Programming Language Concept (Strom & Yemini, 1986)
License
MIT