std.io.FixedBufferStream — Stream sobre Buffer Fixo
O FixedBufferStream é um tipo que implementa as interfaces Reader e Writer sobre um buffer de tamanho fixo em memória. Ele é ideal para cenários onde você precisa de um writer/reader sem alocação dinâmica, como formatar strings, testes ou construir dados em buffers pré-alocados.
Visão Geral
O FixedBufferStream mantém um cursor de posição que avança conforme dados são lidos ou escritos. Ele funciona sobre um slice de bytes existente, sem fazer nenhuma alocação própria.
const std = @import("std");
// Sobre um buffer mutável (leitura e escrita)
var buf: [1024]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
// Sobre um slice constante (somente leitura)
const dados = "Olá, mundo!";
var stream_leitura = std.io.fixedBufferStream(dados);
Tipo Principal
FixedBufferStream
pub fn FixedBufferStream(comptime Buffer: type) type {
return struct {
buffer: Buffer,
pos: usize,
// Obtém o writer (somente para buffers mutáveis)
pub fn writer(self: *Self) Writer;
// Obtém o reader
pub fn reader(self: *Self) Reader;
// Retorna o slice de bytes já escritos (do início até pos)
pub fn getWritten(self: Self) []const u8;
// Reseta a posição para o início
pub fn reset(self: *Self) void;
// Reposiciona o cursor
pub fn seekTo(self: *Self, pos: u64) void;
// Move o cursor relativamente à posição atual
pub fn seekBy(self: *Self, amt: i64) void;
// Retorna a posição atual
pub fn getPos(self: Self) u64;
// Retorna os bytes ainda não lidos/escritos
pub fn getEndPos(self: Self) u64;
};
}
Função de Conveniência
pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(@TypeOf(buffer))
Cria um FixedBufferStream a partir de um slice mutável ([]u8) ou constante ([]const u8).
Exemplo 1: Formatação de String sem Alocação
O uso mais comum é formatar strings em um buffer na stack:
const std = @import("std");
fn formatarMensagem(nome: []const u8, pontos: u32) []const u8 {
var buf: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
writer.print("Jogador '{s}' marcou {d} pontos!", .{ nome, pontos }) catch
return "Erro na formatação";
return stream.getWritten();
}
pub fn main() void {
const msg = formatarMensagem("Ana", 1500);
std.debug.print("{s}\n", .{msg});
}
Nota: Cuidado com o tempo de vida do buffer! No exemplo acima, o slice retornado aponta para a stack da função. Em código real, passe o buffer por parâmetro.
Exemplo 2: Construção de Dados em Memória
Usando FixedBufferStream para construir um pacote de protocolo:
const std = @import("std");
const TipoPacote = enum(u8) {
handshake = 0x01,
dados = 0x02,
desconectar = 0x03,
};
fn construirPacote(buf: []u8, tipo: TipoPacote, payload: []const u8) ![]const u8 {
var stream = std.io.fixedBufferStream(buf);
const writer = stream.writer();
// Cabeçalho: magic (2 bytes) + tipo (1 byte) + tamanho payload (4 bytes)
try writer.writeAll(&[_]u8{ 0xZI, 0x47 }); // magic bytes
try writer.writeByte(@intFromEnum(tipo));
try writer.writeInt(u32, @intCast(payload.len), .big);
// Payload
try writer.writeAll(payload);
return stream.getWritten();
}
pub fn main() !void {
var buf: [1024]u8 = undefined;
const pacote = try construirPacote(&buf, .dados, "Olá, servidor!");
std.debug.print("Pacote: {d} bytes\n", .{pacote.len});
for (pacote) |byte| {
std.debug.print("{x:0>2} ", .{byte});
}
std.debug.print("\n", .{});
}
Exemplo 3: Leitura de Dados de um Buffer
Usando FixedBufferStream como reader sobre dados existentes:
const std = @import("std");
fn parsearCabecalho(dados: []const u8) !void {
var stream = std.io.fixedBufferStream(dados);
const reader = stream.reader();
const versao = try reader.readInt(u16, .big);
const flags = try reader.readByte();
const tamanho = try reader.readInt(u32, .big);
std.debug.print("Versão: {d}\n", .{versao});
std.debug.print("Flags: 0x{x:0>2}\n", .{flags});
std.debug.print("Tamanho: {d}\n", .{tamanho});
// Ler o restante como texto
var texto_buf: [256]u8 = undefined;
const n = try reader.read(&texto_buf);
if (n > 0) {
std.debug.print("Dados: {s}\n", .{texto_buf[0..n]});
}
}
pub fn main() !void {
// Simular dados recebidos
const dados = [_]u8{
0x00, 0x01, // versão = 1
0xFF, // flags
0x00, 0x00, 0x00, 0x05, // tamanho = 5
'H', 'e', 'l', 'l', 'o', // dados
};
try parsearCabecalho(&dados);
}
Padrões Comuns
Formatação Segura com bufPrint
Para formatação simples, std.fmt.bufPrint é mais direto, mas FixedBufferStream é melhor para escritas incrementais:
// Simples: use bufPrint
const resultado = try std.fmt.bufPrint(&buf, "Valor: {d}", .{42});
// Incremental: use FixedBufferStream
var stream = std.io.fixedBufferStream(&buf);
const w = stream.writer();
try w.writeAll("Início ");
try w.print("meio {d} ", .{42});
try w.writeAll("fim");
const resultado2 = stream.getWritten();
Testes com FixedBufferStream
Ideal para testar funções que escrevem em qualquer writer:
test "função de formatação" {
var buf: [1024]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try minhaFuncaoDeEscrita(stream.writer());
try std.testing.expectEqualStrings("saída esperada", stream.getWritten());
}
Verificação de Espaço
O FixedBufferStream retorna erro error.NoSpaceLeft se o buffer estiver cheio:
var buf: [10]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
writer.writeAll("Este texto é longo demais") catch |err| {
// err == error.NoSpaceLeft
std.debug.print("Buffer cheio! Escritos: {d} bytes\n", .{stream.getWritten().len});
};
Módulos Relacionados
- std.io — Visão geral do módulo de I/O
- std.io.Writer — Interface Writer
- std.io.Reader — Interface Reader
- std.io Buffered — BufferedReader/Writer para I/O de disco
- std.fmt — Formatação de strings