Compressão com zlib/deflate em Zig

Introdução

Compressão de dados é uma necessidade comum em aplicações de rede, armazenamento e processamento de arquivos. Zig oferece suporte a deflate/gzip diretamente na biblioteca padrão via std.compress, e também permite usar a libz do sistema via interoperabilidade C.

Para interop com C em geral, veja Chamar Funções C de Zig e Interoperabilidade C-Zig.

Pré-requisitos

Compressão com std.compress (Deflate)

A biblioteca padrão de Zig inclui implementação de deflate:

const std = @import("std");

pub fn comprimir(allocator: std.mem.Allocator, dados: []const u8) ![]u8 {
    var resultado = std.ArrayList(u8).init(allocator);
    errdefer resultado.deinit();

    var compressor = try std.compress.deflate.compressor(
        resultado.writer(),
        .{},
    );
    try compressor.writer().writeAll(dados);
    try compressor.finish();

    return resultado.toOwnedSlice();
}

pub fn descomprimir(allocator: std.mem.Allocator, dados_comprimidos: []const u8) ![]u8 {
    var stream = std.io.fixedBufferStream(dados_comprimidos);
    var decompressor = std.compress.deflate.decompressor(stream.reader());

    var resultado = std.ArrayList(u8).init(allocator);
    errdefer resultado.deinit();

    const reader = decompressor.reader();
    while (true) {
        var buf: [4096]u8 = undefined;
        const n = reader.read(&buf) catch |err| switch (err) {
            error.EndOfStream => break,
            else => return err,
        };
        if (n == 0) break;
        try resultado.appendSlice(buf[0..n]);
    }

    return resultado.toOwnedSlice();
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const original = "Zig Brasil - linguagem de programação de sistemas moderna e eficiente!";

    const comprimido = try comprimir(allocator, original);
    defer allocator.free(comprimido);

    std.debug.print("Original: {} bytes\n", .{original.len});
    std.debug.print("Comprimido: {} bytes\n", .{comprimido.len});

    const restaurado = try descomprimir(allocator, comprimido);
    defer allocator.free(restaurado);

    std.debug.print("Restaurado: {s}\n", .{restaurado});
    std.debug.print("Igual: {}\n", .{std.mem.eql(u8, original, restaurado)});
}

Compressão Gzip

Para gzip (usado em HTTP e arquivos .gz):

const std = @import("std");

pub fn comprimirGzip(allocator: std.mem.Allocator, dados: []const u8) ![]u8 {
    var resultado = std.ArrayList(u8).init(allocator);
    errdefer resultado.deinit();

    var compressor = try std.compress.gzip.compressor(
        resultado.writer(),
        .{},
    );
    try compressor.writer().writeAll(dados);
    try compressor.finish();

    return resultado.toOwnedSlice();
}

pub fn descomprimirGzip(allocator: std.mem.Allocator, dados: []const u8) ![]u8 {
    var stream = std.io.fixedBufferStream(dados);
    var decompressor = std.compress.gzip.decompressor(stream.reader());

    var resultado = std.ArrayList(u8).init(allocator);
    errdefer resultado.deinit();

    while (true) {
        var buf: [4096]u8 = undefined;
        const n = decompressor.reader().read(&buf) catch break;
        if (n == 0) break;
        try resultado.appendSlice(buf[0..n]);
    }

    return resultado.toOwnedSlice();
}

Usando libz do Sistema via @cImport

Para projetos que precisam de compatibilidade total com zlib:

const std = @import("std");
const c = @cImport(@cInclude("zlib.h"));

pub fn comprimirZlib(dest: []u8, src: []const u8) ![]u8 {
    var dest_len: c_ulong = @intCast(dest.len);
    const resultado = c.compress(
        dest.ptr,
        &dest_len,
        src.ptr,
        @intCast(src.len),
    );

    if (resultado != c.Z_OK) return error.CompressaoFalhou;

    return dest[0..@intCast(dest_len)];
}

No build.zig, linkar com zlib:

exe.linkSystemLibrary("z");
exe.linkLibC();

Comprimir Arquivo

const std = @import("std");

pub fn comprimirArquivo(
    allocator: std.mem.Allocator,
    caminho_entrada: []const u8,
    caminho_saida: []const u8,
) !void {
    const entrada = try std.fs.cwd().openFile(caminho_entrada, .{});
    defer entrada.close();

    const saida = try std.fs.cwd().createFile(caminho_saida, .{});
    defer saida.close();

    var compressor = try std.compress.gzip.compressor(saida.writer(), .{});

    var buf: [8192]u8 = undefined;
    while (true) {
        const n = try entrada.read(&buf);
        if (n == 0) break;
        try compressor.writer().writeAll(buf[0..n]);
    }
    try compressor.finish();
}

Teste

test "comprimir e descomprimir" {
    const allocator = std.testing.allocator;

    const original = "Dados de teste para compressão em Zig";
    const comprimido = try comprimir(allocator, original);
    defer allocator.free(comprimido);

    try std.testing.expect(comprimido.len < original.len);

    const restaurado = try descomprimir(allocator, comprimido);
    defer allocator.free(restaurado);

    try std.testing.expectEqualStrings(original, restaurado);
}

Veja Testes Unitários Básicos para mais sobre testes. Para leitura e escrita de arquivos, consulte Ler Arquivo e Escrever em Arquivo.

Continue aprendendo Zig

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