Библиотека реализующая конечный автомат. Go >= 1.27
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alexander Neonxp Kiryukhin 05547f6376
All checks were successful
checks / Vet / Test / Lint (push) Successful in 2m8s
chore(ci): переезд на раннеры alt
- Метка alt маппится на образ gitrepo.ru/neonxp/base, container: больше не нужен
- Три раннера вместо одного последовательного - параллелизм
2026-08-30 13:08:49 +03:00
.forgejo/workflows chore(ci): переезд на раннеры alt 2026-08-30 13:08:49 +03:00
.golangci.yml feature: реализация библиотеки конечного автомата 2026-08-30 11:34:26 +03:00
fsm.go refactor(fsm): оставлен только функциональный режим 2026-08-30 12:31:45 +03:00
fsm_test.go refactor(fsm): оставлен только функциональный режим 2026-08-30 12:31:45 +03:00
go.mod feature: реализация библиотеки конечного автомата 2026-08-30 11:34:26 +03:00
LICENSE feature: реализация библиотеки конечного автомата 2026-08-30 11:34:26 +03:00
README.md refactor(fsm): оставлен только функциональный режим 2026-08-30 12:31:45 +03:00
types.go refactor(fsm): оставлен только функциональный режим 2026-08-30 12:31:45 +03:00

fsm - Конечный автомат для Go

License: GPL
v3

English version below

fsm - это реализация конечного автомата (Finite State Machine) для языка программирования Go, в которой бизнес-логика и решение о переходе живут внутри функций состояний. Машина исполняет состояния одну за другой и валидирует каждый переход по объявленным рёбрам графа.

Особенности

  • Минимальный API: одна структура и два метода - On и Run
  • Функциональные состояния: бизнес-логика и решение о переходе - внутри функции состояния, машина только исполняет и валидирует
  • Валидация переходов: переход по необъявлённому ребру - ошибка ErrInvalidTransition, состояние не меняется
  • Zero-value ready: автомат не требует конструктора - нулевое значение fsm.FSM готово к использованию
  • Никаких паник: все ошибки возвращаются как error с сентинелами ErrInvalidTransition и ErrDuplicateEdge
  • Потокобезопасность: все операции синхронизированы с использованием sync.Mutex

Установка

go get go.neonxp.ru/fsm

Использование

package main

import (
	"context"
	"fmt"
	"log"

	"go.neonxp.ru/fsm"
)

var idle, running fsm.State

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Функция состояния содержит бизнес-логику и решает, куда переходить
	idle = fsm.State(func(ctx context.Context) *fsm.State {
		select {
		case <-tasks:
			return &running
		case <-ctx.Done():
			return nil // терминальное состояние
		}
	})

	running = fsm.State(func(ctx context.Context) *fsm.State {
		if err := process(ctx); err != nil {
			return &idle // ошибка - возвращаем задачу
		}
		return nil
	})

	var m fsm.FSM

	// Объявляем допустимые переходы
	must(m.On(&idle, &running))
	must(m.On(&running, &idle))

	// Машина исполняет состояния и валидирует каждый переход
	if err := m.Run(ctx, &idle); err != nil {
		log.Fatal(err)
	}
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

API

type State func(ctx context.Context) *State

Функциональное состояние: инкапсулирует бизнес-логику и решение о переходе, возвращая следующее состояние. nil - терминальное состояние. Состояния объявляются переменными, их идентичность - адрес переменной.

func (f *FSM) On(from, to *State) error

Объявляет допустимый переход между состояниями from и to. При дубликате ребра возвращает ошибку, обёртывающую ErrDuplicateEdge. Переход в терминальное состояние (nil) объявлять не нужно.

func (f *FSM) Run(ctx context.Context, initial *State) error

Запускает цикл: исполняет текущее состояние, валидирует переход к возвращённому состоянию и повторяет, пока функция состояния не вернёт nil или контекст не будет отменён. При необъявленном переходе возвращает ошибку, обёртывающую ErrInvalidTransition.

Нюансы

  • Идентичность функционального состояния - адрес переменной, поэтому функции состояний возвращают указатели на объявленные переменные (return &running)
  • Переход в терминальное состояние (nil) всегда допустим и не объявляется
  • Нулевое значение FSM без объявленных рёбер допускает только переход в терминальное состояние
  • Копирование автомата запрещено: используйте указатель *FSM (проверяется go vet)
  • nil-контекст безопасен: контекст передаётся только в функции состояний

Лицензия

Этот проект лицензирован в соответствии с GNU General Public License версии 3 (GPLv3). Подробности смотрите в файле LICENSE.

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

Ссылки

English Version

fsm is an implementation of a Finite State Machine for the Go programming language in which the business logic and the transition decision live inside the state functions. The machine executes the states one by one and validates every transition against the declared edges of the graph.

Features

  • Minimal API: one struct and two methods - On and Run
  • Functional states: the business logic and the transition decision live inside the state function, the machine only executes and validates
  • Transition validation: a transition over an undeclared edge is an ErrInvalidTransition error, the state remains unchanged
  • Zero-value ready: The machine does not require a constructor - the zero value of fsm.FSM is ready to use
  • No panics: All errors are returned as error with the ErrInvalidTransition and ErrDuplicateEdge sentinels
  • Thread-safe: All operations are synchronized using sync.Mutex

Installation

go get go.neonxp.ru/fsm

Usage

package main

import (
	"context"
	"log"

	"go.neonxp.ru/fsm"
)

var idle, running fsm.State

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// The state function contains the business logic and decides where to
	// transition
	idle = fsm.State(func(ctx context.Context) *fsm.State {
		select {
		case <-tasks:
			return &running
		case <-ctx.Done():
			return nil // terminal state
		}
	})

	running = fsm.State(func(ctx context.Context) *fsm.State {
		if err := process(ctx); err != nil {
			return &idle // error - put the task back
		}
		return nil
	})

	var m fsm.FSM

	// Declare allowed transitions
	must(m.On(&idle, &running))
	must(m.On(&running, &idle))

	// The machine executes the states and validates every transition
	if err := m.Run(ctx, &idle); err != nil {
		log.Fatal(err)
	}
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

API

type State func(ctx context.Context) *State

A functional state: encapsulates the business logic and the transition decision, returning the next state. nil is a terminal state. States are declared as variables, their identity is the address of the variable.

func (f *FSM) On(from, to *State) error

Declares an allowed transition between states from and to. Returns an error wrapping ErrDuplicateEdge on a duplicate edge. A transition to a terminal state (nil) needs no declaration.

func (f *FSM) Run(ctx context.Context, initial *State) error

Starts the loop: executes the current state, validates the transition to the returned state and repeats until a state function returns nil or the context is canceled. Returns an error wrapping ErrInvalidTransition on an undeclared transition.

Caveats

  • The identity of a functional state is the address of the variable, so state functions return pointers to the declared variables (return &running)
  • A transition to a terminal state (nil) is always allowed and is not declared
  • The zero value of FSM without declared edges allows only transitions to the terminal state
  • Copying the machine is forbidden: use the *FSM pointer (checked by go vet)
  • A nil context is safe: the context is only passed to state functions

License

This project is licensed under the GNU General Public License version 3 (GPLv3). See the LICENSE file for details.

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.