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
- Strategy — Comportamento variável sem estados
- Command — Encapsular transições como objetos
- Enums e Unions — Tagged unions em detalhe
- Error Handling — Estados de erro
- Receitas — Exemplos práticos de parsers