Last Updated: November 22, 2025
This document provides comprehensive guidance on using Finite State Machines (FSMs) in the Cure programming language.
Cure provides first-class support for Finite State Machines (FSMs) as a core language feature. FSMs in Cure compile directly to BEAM gen_statem processes and provide:
Here's a minimal FSM example:
module SimpleFSM do
export [main/0]
import Std.Fsm [fsm_spawn/2, fsm_cast/2, fsm_advertise/2, fsm_state/1]
import Std.Pair [pair/2]
# Define payload record
record SimplePayload do
counter: Int
end
# Define FSM with transitions
fsm SimplePayload{counter: 0} do
Idle --> |start| Running
Running --> |stop| Idle
end
def main(): Int =
# Spawn FSM
let initial_data = SimplePayload{counter: 0}
let fsm_pid = fsm_spawn(:SimplePayload, initial_data)
# Name it
let _ = fsm_advertise(fsm_pid, :simple_fsm)
# Send event
let event = pair(:start, [])
let _ = fsm_cast(:simple_fsm, event)
# Query state
let current_state = fsm_state(:simple_fsm) # Returns :Running
0
end
Every FSM definition requires:
1. A payload record
2. An fsm block with initial payload values
3. Transition definitions using arrow syntax
# 1. Define payload record
record MyPayload do
field1: Type1
field2: Type2
end
# 2. Define FSM
fsm MyPayload{field1: value1, field2: value2} do
StateA --> |event1| StateB
StateB --> |event2| StateC
StateC --> |event3| StateA
end
Transitions use the format: FromState --> |event| ToState
|event| notationExample:
fsm TrafficPayload{cycles: 0} do
Red --> |timer| Green # Red is initial state
Green --> |timer| Yellow
Yellow --> |timer| Red
end
A state can transition to itself:
fsm DoorPayload{locked: true} do
Locked --> |unlock| Unlocked
Locked --> |knock| Locked # Self-transition
Unlocked --> |lock| Locked
end
Payloads are records that carry data through FSM transitions. They must be defined before the FSM.
record TrafficPayload do
cycles_completed: Int
timer_events: Int
emergency_stops: Int
end
Provide initial values in the FSM declaration:
fsm TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0} do
# transitions...
end
Payloads are useful for:
- Tracking statistics (counters, timestamps)
- Storing configuration
- Maintaining state-specific data
- Debugging information
import Std.Fsm [fsm_spawn/2, fsm_cast/2, fsm_advertise/2, fsm_state/1]
import Std.Pair [pair/2]
Use fsm_spawn/2 with the payload type and initial data:
let initial_data = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
let fsm_pid = fsm_spawn(:TrafficPayload, initial_data)
Make the FSM accessible by name:
let _ = fsm_advertise(fsm_pid, :traffic_light)
# Now you can use :traffic_light instead of fsm_pid
Events are sent using pairs (tuples):
# Create event with empty data list
let empty_list = []
let event = pair(:timer, empty_list)
# Send to FSM
let _ = fsm_cast(:traffic_light, event)
Get the current state:
let current_state = fsm_state(:traffic_light)
# Returns atom like :Red, :Green, or :Yellow
Here's the traffic light example from examples/06_fsm_traffic_light.cure:
module TrafficLightFSM do
export [main/0]
import Std.Fsm [fsm_spawn/2, fsm_cast/2, fsm_advertise/2, fsm_state/1]
import Std.Io [println/1]
import Std.Pair [pair/2]
# Payload record - tracks traffic light statistics
record TrafficPayload do
cycles_completed: Int
timer_events: Int
emergency_stops: Int
end
# Define the TrafficLight FSM
# Initial state is Red (first state in transitions)
# Events: :timer (normal progression), :emergency (immediate red)
fsm TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0} do
Red --> |timer| Green
Red --> |emergency| Red
Green --> |timer| Yellow
Green --> |emergency| Red
Yellow --> |timer| Red
Yellow --> |emergency| Red
end
# Main demonstration
def main(): Int =
println("=== Traffic Light FSM Demo ===")
println("")
# Initialize FSM with starting data
let initial_data = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
let fsm_pid = fsm_spawn(:TrafficPayload, initial_data)
# Give the FSM a friendly name
let adv_result = fsm_advertise(fsm_pid, :traffic_light)
# Check initial state (should be Red - first in transition list)
println("Initial state:")
let state0 = fsm_state(:traffic_light)
println("State: Red (expected)")
println("")
# Scenario 1: Normal timer progression Red -> Green
println("Scenario 1: Timer event from Red")
let empty1 = []
let event1 = pair(:timer, empty1)
let cast1 = fsm_cast(:traffic_light, event1)
let state1 = fsm_state(:traffic_light)
println("State: Green (expected)")
println("")
# Scenario 2: Normal timer progression Green -> Yellow
println("Scenario 2: Timer event from Green")
let empty2 = []
let event2 = pair(:timer, empty2)
let cast2 = fsm_cast(:traffic_light, event2)
let state2 = fsm_state(:traffic_light)
println("State: Yellow (expected)")
println("")
# Scenario 3: Emergency from Yellow -> Red
println("Scenario 3: Emergency stop from Yellow")
let empty3 = []
let event3 = pair(:emergency, empty3)
let cast3 = fsm_cast(:traffic_light, event3)
let state3 = fsm_state(:traffic_light)
println("State: Red (expected - emergency stop)")
println("")
println("=== Demo Complete ===")
0
end
fsm_spawn(fsm_type: Atom, initial_data: Any): PidSpawn a new FSM instance.
- fsm_type: Payload record type name (e.g., :TrafficPayload)
- initial_data: Initial payload record instance
- Returns: Process ID
fsm_cast(target: Pid | Atom, event: Pair): AnySend an event to an FSM (asynchronous).
- target: FSM Pid or registered name
- event: Pair of (event_name, event_data) created with pair/2
- Returns: Cast result
fsm_advertise(pid: Pid, name: Atom): AnyRegister a name for an FSM process.
- pid: FSM process ID
- name: Atom to register (e.g., :my_fsm)
- Returns: Advertisement result
fsm_state(target: Pid | Atom): AtomQuery current FSM state.
- target: FSM Pid or registered name
- Returns: Current state atom
fsm_stop(pid: Pid): AnyStop an FSM instance gracefully.
fsm_info(pid: Pid): AnyGet detailed FSM information (state, data, history).
fsm_is_alive(pid: Pid): BoolCheck if FSM process is running.
fsm_advertise/2 for important FSMsfsm_is_alive/1pair/2fsm blockfsm_state/1 to confirm the current state before sending eventsStd.Fsm functionsStd.Pair [pair/2] for creating eventsStd.Io [println/1] if you need to print outputFor questions or issues, consult the main Cure documentation or examine the FSM runtime source code in src/fsm/.