Ring Buffer (Buffer Circular) em Zig — Implementação Completa

Ring Buffer (Buffer Circular) em Zig — Implementação Completa

Um Ring Buffer (buffer circular) é uma estrutura de dados de tamanho fixo que funciona como se o final do array estivesse conectado ao início. Quando o buffer está cheio e um novo elemento é escrito, ele sobrescreve o mais antigo. É extremamente eficiente para filas de produtores/consumidores, streaming de dados, logs rotativos e comunicação entre threads.

Conceito

Ring Buffer com capacidade 6:

Estado após inserir A, B, C, D:
  [A][B][C][D][ ][ ]
   ^           ^
   leitura     escrita

Após ler A e B:
  [ ][ ][C][D][ ][ ]
         ^     ^
         leit  escrita

Após inserir E, F, G (wrap around):
  [G][ ][C][D][E][F]
   ^  ^
   es leit → Escrita voltou ao início!

Índices circulares: pos = (pos + 1) % capacidade

Implementação em Zig

const std = @import("std");

/// Ring Buffer genérico de tamanho fixo (comptime).
pub fn RingBuffer(comptime T: type, comptime capacidade: usize) type {
    return struct {
        const Self = @This();

        dados: [capacidade]T = undefined,
        leitura: usize = 0,    // Índice do próximo elemento a ler
        escrita: usize = 0,    // Índice do próximo espaço a escrever
        contagem: usize = 0,   // Elementos atualmente no buffer

        /// Escreve um elemento no buffer.
        /// Retorna o elemento sobrescrito se o buffer estava cheio.
        pub fn escrever(self: *Self, item: T) ?T {
            var sobrescrito: ?T = null;

            if (self.contagem == capacidade) {
                sobrescrito = self.dados[self.leitura];
                self.leitura = (self.leitura + 1) % capacidade;
            } else {
                self.contagem += 1;
            }

            self.dados[self.escrita] = item;
            self.escrita = (self.escrita + 1) % capacidade;

            return sobrescrito;
        }

        /// Lê e remove o elemento mais antigo.
        pub fn ler(self: *Self) ?T {
            if (self.contagem == 0) return null;

            const item = self.dados[self.leitura];
            self.leitura = (self.leitura + 1) % capacidade;
            self.contagem -= 1;
            return item;
        }

        /// Espia o próximo elemento sem removê-lo.
        pub fn espiar(self: *const Self) ?T {
            if (self.contagem == 0) return null;
            return self.dados[self.leitura];
        }

        /// Verifica se está cheio.
        pub fn cheio(self: *const Self) bool {
            return self.contagem == capacidade;
        }

        /// Verifica se está vazio.
        pub fn vazio(self: *const Self) bool {
            return self.contagem == 0;
        }

        /// Retorna número de elementos.
        pub fn tamanho(self: *const Self) usize {
            return self.contagem;
        }

        /// Retorna espaço disponível.
        pub fn espacoLivre(self: *const Self) usize {
            return capacidade - self.contagem;
        }

        /// Limpa o buffer.
        pub fn limpar(self: *Self) void {
            self.leitura = 0;
            self.escrita = 0;
            self.contagem = 0;
        }

        /// Itera sobre os elementos (do mais antigo ao mais recente).
        pub fn iterar(self: *const Self) Iterator {
            return .{ .buffer = self, .pos = self.leitura, .restante = self.contagem };
        }

        pub const Iterator = struct {
            buffer: *const Self,
            pos: usize,
            restante: usize,

            pub fn next(it: *Iterator) ?T {
                if (it.restante == 0) return null;
                const item = it.buffer.dados[it.pos];
                it.pos = (it.pos + 1) % capacidade;
                it.restante -= 1;
                return item;
            }
        };
    };
}

/// Ring Buffer com alocação dinâmica.
pub fn RingBufferDinamico(comptime T: type) type {
    return struct {
        const Self = @This();

        dados: []T,
        leitura: usize,
        escrita: usize,
        contagem: usize,
        capacidade: usize,
        allocator: std.mem.Allocator,

        pub fn init(allocator: std.mem.Allocator, capacidade: usize) !Self {
            return .{
                .dados = try allocator.alloc(T, capacidade),
                .leitura = 0,
                .escrita = 0,
                .contagem = 0,
                .capacidade = capacidade,
                .allocator = allocator,
            };
        }

        pub fn deinit(self: *Self) void {
            self.allocator.free(self.dados);
        }

        pub fn escrever(self: *Self, item: T) ?T {
            var sobrescrito: ?T = null;
            if (self.contagem == self.capacidade) {
                sobrescrito = self.dados[self.leitura];
                self.leitura = (self.leitura + 1) % self.capacidade;
            } else {
                self.contagem += 1;
            }
            self.dados[self.escrita] = item;
            self.escrita = (self.escrita + 1) % self.capacidade;
            return sobrescrito;
        }

        pub fn ler(self: *Self) ?T {
            if (self.contagem == 0) return null;
            const item = self.dados[self.leitura];
            self.leitura = (self.leitura + 1) % self.capacidade;
            self.contagem -= 1;
            return item;
        }
    };
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Ring buffer estático
    var buf = RingBuffer(u8, 5){};

    try stdout.writeAll("=== Ring Buffer (capacidade=5) ===\n");

    // Escreve dados
    for ("HELLO WORLD") |c| {
        const sobrescrito = buf.escrever(c);
        if (sobrescrito) |s| {
            try stdout.print("  Escreveu '{c}', sobrescreveu '{c}'\n", .{ c, s });
        } else {
            try stdout.print("  Escreveu '{c}'\n", .{c});
        }
    }

    try stdout.print("\nTamanho: {d}/{d}\n", .{ buf.tamanho(), @as(usize, 5) });

    // Lê todos os elementos
    try stdout.writeAll("Conteúdo: ");
    var iter = buf.iterar();
    while (iter.next()) |c| {
        try stdout.print("{c}", .{c});
    }
    try stdout.writeAll("\n");

    // Exemplo prático: log rotativo
    try stdout.writeAll("\n=== Log Rotativo ===\n");
    var log_buf = RingBuffer([]const u8, 4){};

    const mensagens = [_][]const u8{
        "Sistema iniciado",
        "Conexão recebida",
        "Query executada",
        "Cache atualizado",
        "Erro de timeout",    // Vai sobrescrever a mais antiga
        "Reconexão OK",       // Vai sobrescrever a segunda mais antiga
    };

    for (mensagens) |msg| {
        _ = log_buf.escrever(msg);
    }

    try stdout.writeAll("Últimas mensagens no log:\n");
    var log_iter = log_buf.iterar();
    while (log_iter.next()) |msg| {
        try stdout.print("  > {s}\n", .{msg});
    }
}

Análise de Complexidade

OperaçãoTempoEspaço
EscreverO(1)O(1)
LerO(1)O(1)
EspiarO(1)O(1)
Espaço totalO(n)

Casos de Uso

  • Audio/Video streaming — buffer de reprodução
  • Logging — últimas N mensagens
  • Comunicação entre threads — fila lock-free
  • Rede — buffer de pacotes
  • Sensores/IoT — últimas N leituras

Recursos Relacionados

Continue aprendendo Zig

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