go-fsm
A minimal, generic Finite State Machine for Go.
Philosophy
Most FSM libraries come with events, sources, destinations, callbacks, and background contexts.
This one doesn't.
A finite state machine only needs states - each state has a single transition function that returns the next state name.
That's it.
Installation
go get codeberg.org/datek/fsm
Usage
Define your shared data type, create states with transition functions, and run the machine. The first state in the slice is the initial state. The machine stops when a transition returns StateFinal.
package main
import (
"fmt"
"codeberg.org/datek/fsm"
)
type Data struct {
count int
}
func main() {
increment := &fsm.State[Data]{
Name: "increment",
Transit: func(d *Data) fsm.StateName {
d.count++
if d.count >= 3 {
return fsm.StateFinal
}
return "increment"
},
}
data := &Data{}
machine := fsm.NewFSM([]*fsm.State[Data]{increment}, data)
machine.Run()
fmt.Printf("machine.CurrentStateName(): %v\n", machine.CurrentStateName())
fmt.Printf("data.count: %v\n", data.count)
}
// machine.CurrentStateName(): STATE_FINAL
// data.count: 3
API
NewFSM(states []*State[T], data *T) FSM
Creates a new FSM. The first element of states is the initial state. data is a pointer to shared context passed into every transition.
FSM.Run()
Runs the machine until a transition returns StateFinal. If a transition returns a state name that was not registered, the call will panic.
FSM.CurrentStateName() StateName
Returns the name of the current state.
StateFinal
The sentinel value "STATE_FINAL" — returning it from a transition stops the machine.
Error handling
Transit does not return an error. All error handling is the caller's responsibility inside the transition function. The recommended pattern is to store errors in the shared data and check them in subsequent states:
type Data struct {
result string
err error
}
fetch := &fsm.State[Data]{
Name: "fetch",
Transit: func(d *Data) fsm.StateName {
d.result, d.err = doSomething()
if d.err != nil {
return fsm.StateFinal
}
return "process"
},
}