---
title: "Cheatsheet: State Machine em Zig"
url: "https://ziglang.com.br/padroes/cheatsheet-state-machine-em-zig/"
markdown_url: "https://ziglang.com.br/padroes/cheatsheet-state-machine-em-zig.MD"
description: "Design pattern State Machine implementado em Zig: máquinas de estado com tagged unions, transições type-safe e autômatos finitos. Guia completo em português."
date: "2026-02-21"
author: "Zig Brasil"
---

# Cheatsheet: State Machine em Zig

Design pattern State Machine implementado em Zig: máquinas de estado com tagged unions, transições type-safe e autômatos finitos. Guia completo em português.


# State Machine em Zig

Máquinas de estado são uma das implementações mais elegantes em Zig, graças às **tagged unions**. O compilador garante que todos os estados e transições sejam tratados exaustivamente via `switch`, eliminando toda uma classe de bugs comuns em máquinas de estado.

## Quando Usar

- Protocolos de comunicação (HTTP, WebSocket, TCP)
- Parsing de dados estruturados
- Fluxo de interface (telas, formulários, wizards)
- Game states (menu, jogando, pausado, game over)
- Processos de negócio com estados definidos

## Máquina de Estado com Tagged Union

```zig
const std = @import("std");

const EstadoConexao = union(enum) {
    desconectado,
    conectando: struct { tentativas: u8 },
    conectado: struct { socket_fd: i32 },
    erro: struct { mensagem: []const u8 },

    pub fn processar(self: *EstadoConexao, evento: Evento) void {
        self.* = switch (self.*) {
            .desconectado => switch (evento) {
                .conectar => .{ .conectando = .{ .tentativas = 1 } },
                else => self.*,
            },
            .conectando => |info| switch (evento) {
                .sucesso => |fd| .{ .conectado = .{ .socket_fd = fd } },
                .falha => |msg| blk: {
                    if (info.tentativas >= 3) {
                        break :blk EstadoConexao{ .erro = .{ .mensagem = msg } };
                    }
                    break :blk EstadoConexao{
                        .conectando = .{ .tentativas = info.tentativas + 1 },
                    };
                },
                else => self.*,
            },
            .conectado => switch (evento) {
                .desconectar => .desconectado,
                .falha => |msg| .{ .erro = .{ .mensagem = msg } },
                else => self.*,
            },
            .erro => switch (evento) {
                .conectar => .{ .conectando = .{ .tentativas = 1 } },
                .resetar => .desconectado,
                else => self.*,
            },
        };
    }

    pub fn descricao(self: EstadoConexao) []const u8 {
        return switch (self) {
            .desconectado => "Desconectado",
            .conectando => "Conectando...",
            .conectado => "Conectado",
            .erro => "Erro",
        };
    }
};

const Evento = union(enum) {
    conectar,
    desconectar,
    sucesso: i32,
    falha: []const u8,
    resetar,
};

pub fn main() void {
    var estado = EstadoConexao.desconectado;

    std.debug.print("Estado: {s}\n", .{estado.descricao()});

    estado.processar(.conectar);
    std.debug.print("Estado: {s}\n", .{estado.descricao()});

    estado.processar(.{ .sucesso = 42 });
    std.debug.print("Estado: {s}\n", .{estado.descricao()});

    estado.processar(.desconectar);
    std.debug.print("Estado: {s}\n", .{estado.descricao()});
}
```

## Parser como Máquina de Estado

```zig
const std = @import("std");

const ParserCSV = struct {
    const Estado = enum {
        inicio_campo,
        dentro_campo,
        dentro_aspas,
        aspas_encontrada,
    };

    estado: Estado = .inicio_campo,
    campos: std.ArrayList([]const u8),
    inicio_atual: usize = 0,

    pub fn init(allocator: std.mem.Allocator) ParserCSV {
        return .{
            .campos = std.ArrayList([]const u8).init(allocator),
        };
    }

    pub fn deinit(self: *ParserCSV) void {
        self.campos.deinit();
    }

    pub fn parsearLinha(self: *ParserCSV, linha: []const u8) !void {
        self.campos.clearRetainingCapacity();
        self.estado = .inicio_campo;
        self.inicio_atual = 0;

        for (linha, 0..) |ch, i| {
            switch (self.estado) {
                .inicio_campo => switch (ch) {
                    '"' => {
                        self.estado = .dentro_aspas;
                        self.inicio_atual = i + 1;
                    },
                    ',' => try self.campos.append(linha[self.inicio_atual..i]),
                    else => self.estado = .dentro_campo,
                },
                .dentro_campo => switch (ch) {
                    ',' => {
                        try self.campos.append(linha[self.inicio_atual..i]);
                        self.inicio_atual = i + 1;
                        self.estado = .inicio_campo;
                    },
                    else => {},
                },
                .dentro_aspas => switch (ch) {
                    '"' => self.estado = .aspas_encontrada,
                    else => {},
                },
                .aspas_encontrada => switch (ch) {
                    '"' => self.estado = .dentro_aspas,
                    ',' => {
                        try self.campos.append(linha[self.inicio_atual .. i - 1]);
                        self.inicio_atual = i + 1;
                        self.estado = .inicio_campo;
                    },
                    else => self.estado = .dentro_campo,
                },
            }
        }
        try self.campos.append(linha[self.inicio_atual..]);
    }
};
```

## Máquina de Estado Comptime para Protocolos

Quando os estados e transições são conhecidos em compile time, você pode usar `comptime` para gerar código de despacho otimizado:

```zig
const std = @import("std");

// Tabela de transições resolvida em comptime
fn criarTabelaTransicoes(comptime Estados: type, comptime Eventos: type) type {
    const num_estados = @typeInfo(Estados).@"enum".fields.len;
    const num_eventos = @typeInfo(Eventos).@"enum".fields.len;

    return struct {
        tabela: [num_estados][num_eventos]?Estados,

        pub fn init() @This() {
            var t: @This() = .{ .tabela = .{.{null} ** num_eventos} ** num_estados };
            return t;
        }

        pub fn addTransicao(
            self: *@This(),
            de: Estados,
            evento: Eventos,
            para: Estados,
        ) void {
            self.tabela[@intFromEnum(de)][@intFromEnum(evento)] = para;
        }

        pub fn transicionar(self: *const @This(), estado: Estados, evento: Eventos) ?Estados {
            return self.tabela[@intFromEnum(estado)][@intFromEnum(evento)];
        }
    };
}
```

Esta abordagem é útil para máquinas de estado com muitos estados e transições, onde o `switch` aninhado ficaria muito longo.

## Considerações de Performance

- **Tagged union é a abordagem mais eficiente**: o compilador Zig gera um `switch` com jump table para unions com enum tag. Transições de estado são tipicamente uma instrução de load e um jump — custo mínimo.
- **Estado com dados embutidos**: o grande diferencial das tagged unions é poder armazenar dados *dentro* do estado. `conectando: struct { tentativas: u8 }` não precisa de alocação separada — os dados vivem dentro da union.
- **Enum simples vs tagged union**: se os estados não precisam de dados associados, use um `enum` simples com `switch`. É mais leve que uma union completa e o compilador pode representar o enum em um único byte se houver menos de 256 valores.
- **Máquina de estado em hot path**: em parsers de alta performance (JSON, HTTP), a máquina de estado é executada para cada byte de entrada. O custo de cada transição deve ser mínimo — prefira `enum` simples com `switch` a unions com dados complexos.

## Erros Comuns

**Não tratar a transição `else => self.*`**: ao adicionar um novo evento ou estado, é fácil esquecer de adicionar as transições correspondentes. Com `else => self.*`, o compilador não vai alertar sobre casos faltando. Para máquinas críticas, use `else => @panic("transição inválida")` em debug para detectar estados não tratados.

**Estado mutável compartilhado entre transitions**: se a função `processar` chama código externo que pode modificar o estado antes do `switch` ser avaliado, você pode ter race conditions. Sempre capture o estado atual em uma variável local antes de processar.

**Confundir estado e transição**: o estado representa *onde* o sistema está; a transição representa *o que aconteceu*. Um evento `conectar` não é um estado — é o evento que causa a transição de `desconectado` para `conectando`.

## Perguntas Frequentes

**Como persistir e restaurar o estado de uma máquina?**
Com tagged unions, serialize o tag e os dados do estado ativo. Use `std.json.stringify` se os dados do estado forem serializáveis, ou serialize manualmente para um formato binário compacto. Na restauração, leia o tag e construa o estado correto.

**Posso ter ações executadas na entrada/saída de estados?**
Sim — adicione hooks `aoEntrar` e `aoSair` que são chamados pela função `processar` durante a transição. Guarde o estado anterior, execute `aoSair` no estado anterior, mude o estado, execute `aoEntrar` no novo estado.

**Como debugar qual transição aconteceu?**
Adicione logging na função `processar`: antes e depois da atribuição `self.*`. Em modo debug, imprima `@tagName(estado_anterior)`, o evento, e `@tagName(self.*)`. Em produção, remova com `if (builtin.mode == .Debug)`.

## Quando Evitar

- Lógica com apenas dois estados (basta um `bool`)
- Quando as transições são lineares e nunca voltam
- Estados que nunca mudam em runtime (use comptime)

## Veja Também

- [Strategy](/padroes/strategy/) — Comportamento variável sem estados
- [Command](/padroes/command/) — Encapsular transições como objetos
- [Enums e Unions](/cheatsheets/enums-unions/) — Tagged unions em detalhe
- [Error Handling](/cheatsheets/error-handling/) — Estados de erro
- [Receitas](/receitas/) — Exemplos práticos de parsers
