Bibliotecas de Serialização em Zig — MessagePack, Protobuf e Mais
Serialização eficiente de dados é crucial para comunicação entre serviços, armazenamento e protocolos de rede. O Zig oferece vantagens únicas para serialização: structs packed com layout de memória controlado, comptime para gerar código de serialização automaticamente e interoperabilidade C para usar bibliotecas existentes. Além do suporte a JSON na std, o ecossistema oferece diversas opções binárias.
Serialização Binária Nativa
O Zig permite serialização direta de structs packed:
const std = @import("std");
const Mensagem = packed struct {
tipo: u8,
flags: u8,
tamanho: u16,
timestamp: u64,
checksum: u32,
};
pub fn serializar(msg: *const Mensagem) [@sizeOf(Mensagem)]u8 {
return @bitCast(msg.*);
}
pub fn deserializar(bytes: [@sizeOf(Mensagem)]u8) Mensagem {
return @bitCast(bytes);
}
pub fn enviarPelaRede(stream: std.net.Stream, msg: *const Mensagem) !void {
const bytes = serializar(msg);
try stream.writeAll(&bytes);
}
pub fn receberDaRede(stream: std.net.Stream) !Mensagem {
var bytes: [@sizeOf(Mensagem)]u8 = undefined;
_ = try stream.readAll(&bytes);
return deserializar(bytes);
}
MessagePack
O MessagePack é um formato binário compacto e rápido, ideal para comunicação entre serviços:
const msgpack = @import("zig-msgpack");
const Produto = struct {
id: u64,
nome: []const u8,
preco: f64,
tags: []const []const u8,
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
const produto = Produto{
.id = 42,
.nome = "Widget Premium",
.preco = 99.90,
.tags = &.{ "eletrônicos", "premium" },
};
// Serializar
var buf: [1024]u8 = undefined;
const encoded = try msgpack.encode(Produto, &buf, produto);
// Deserializar
const decoded = try msgpack.decode(Produto, allocator, encoded);
std.debug.print("Produto: {s} - R${d:.2}\n", .{ decoded.nome, decoded.preco });
}
Protocol Buffers
zig-protobuf
const protobuf = @import("zig-protobuf");
// Gerado a partir de .proto
const Usuario = protobuf.Message(struct {
id: u64 = 0, // field 1
nome: []const u8 = "", // field 2
email: []const u8 = "", // field 3
idade: u32 = 0, // field 4
});
pub fn main() !void {
const allocator = std.heap.page_allocator;
// Criar mensagem
var usuario = Usuario{
.id = 1,
.nome = "Maria",
.email = "maria@exemplo.com",
.idade = 28,
};
// Serializar
var buf: [256]u8 = undefined;
const tamanho = try usuario.encode(&buf);
// Deserializar
const decoded = try Usuario.decode(allocator, buf[0..tamanho]);
std.debug.print("Usuário: {s}\n", .{decoded.nome});
}
CBOR
O CBOR (Concise Binary Object Representation) é um formato binário baseado no modelo de dados JSON:
const cbor = @import("zig-cbor");
pub fn main() !void {
const allocator = std.heap.page_allocator;
// Serializar para CBOR
var buf: [1024]u8 = undefined;
var encoder = cbor.Encoder.init(&buf);
try encoder.encodeMapHeader(3);
try encoder.encodeString("nome");
try encoder.encodeString("João");
try encoder.encodeString("idade");
try encoder.encodeUint(30);
try encoder.encodeString("ativo");
try encoder.encodeBool(true);
const encoded = encoder.getWritten();
// Deserializar
var decoder = cbor.Decoder.init(encoded, allocator);
const valor = try decoder.decode();
_ = valor;
}
Serialização com Comptime
Uma das vantagens mais poderosas do Zig é gerar serializadores em comptime:
fn AutoSerializer(comptime T: type) type {
return struct {
pub fn serialize(value: T, writer: anytype) !void {
const info = @typeInfo(T);
switch (info) {
.Struct => |s| {
inline for (s.fields) |field| {
try serializeField(field.type, @field(value, field.name), writer);
}
},
else => @compileError("Tipo não suportado: " ++ @typeName(T)),
}
}
fn serializeField(comptime FieldType: type, value: FieldType, writer: anytype) !void {
switch (@typeInfo(FieldType)) {
.Int => try writer.writeInt(FieldType, value, .little),
.Float => {
const IntType = std.meta.Int(.unsigned, @bitSizeOf(FieldType));
try writer.writeInt(IntType, @bitCast(value), .little);
},
.Bool => try writer.writeByte(if (value) 1 else 0),
.Pointer => |ptr| {
if (ptr.size == .Slice) {
try writer.writeInt(u32, @intCast(value.len), .little);
try writer.writeAll(value);
}
},
else => {},
}
}
pub fn deserialize(reader: anytype, allocator: std.mem.Allocator) !T {
var result: T = undefined;
const info = @typeInfo(T);
inline for (info.Struct.fields) |field| {
@field(result, field.name) = try deserializeField(
field.type, reader, allocator,
);
}
return result;
}
fn deserializeField(comptime FieldType: type, reader: anytype, allocator: std.mem.Allocator) !FieldType {
_ = allocator;
switch (@typeInfo(FieldType)) {
.Int => return try reader.readInt(FieldType, .little),
.Float => {
const IntType = std.meta.Int(.unsigned, @bitSizeOf(FieldType));
const bits = try reader.readInt(IntType, .little);
return @bitCast(bits);
},
.Bool => return (try reader.readByte()) != 0,
else => return undefined,
}
}
};
}
// Uso
const MeuDado = struct {
id: u32,
valor: f64,
ativo: bool,
};
const Serializer = AutoSerializer(MeuDado);
test "serialização automática" {
var buf: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const original = MeuDado{ .id = 42, .valor = 3.14, .ativo = true };
try Serializer.serialize(original, stream.writer());
stream.pos = 0;
const restaurado = try Serializer.deserialize(stream.reader(), std.testing.allocator);
try std.testing.expectEqual(original.id, restaurado.id);
}
Comparação de Formatos
| Formato | Tamanho | Velocidade | Schema | Legível | Uso Ideal |
|---|---|---|---|---|---|
| JSON | Grande | Média | Não | Sim | APIs web, config |
| MessagePack | Pequeno | Rápida | Não | Não | RPC, cache |
| Protobuf | Pequeno | Rápida | Sim | Não | gRPC, microserviços |
| CBOR | Pequeno | Rápida | Não | Não | IoT, COSE |
| Packed struct | Mínimo | Máxima | Implícito | Não | Protocolos internos |
| FlatBuffers | Médio | Máxima | Sim | Não | Jogos, mobile |
Boas Práticas
- Escolha o formato pelo caso de uso: JSON para interop, MessagePack para performance, Protobuf para evolução de schema
- Versione seus formatos: Adicione campos de versão para compatibilidade futura
- Valide após deserialização: Verifique invariantes dos dados
- Use comptime: Gere serializadores automaticamente para reduzir código manual
- Considere endianness: Use little-endian para x86/ARM, big-endian para rede
Próximos Passos
Explore as bibliotecas JSON para serialização textual, as bibliotecas de compressão para otimizar tamanho, e as bibliotecas de rede para transmissão eficiente. Consulte nossos tutoriais para projetos práticos.