Cheatsheet: State Machine em Zig

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

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

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..]);
    }
};

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.