Cheatsheet: Operações de I/O em Zig

Cheatsheet: Operações de I/O em Zig

Zig oferece um sistema de I/O robusto e performático através da biblioteca padrão std.fs e std.io. O design prioriza controle explícito sobre buffers, tratamento de erros obrigatório e composição de readers/writers via interfaces genéricas.

Stdout, Stdin e Stderr

Escrita em stdout

const std = @import("std");

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

    try stdout.print("Olá, {s}!\n", .{"mundo"});
    try stdout.writeAll("Texto direto sem formatação\n");

    // Escrever bytes brutos
    try stdout.writeAll(&[_]u8{ 72, 105, 10 }); // "Hi\n"

    // Escrever byte único
    try stdout.writeByte('Z');
    try stdout.writeByte('\n');
}

Leitura de stdin

const std = @import("std");

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

    try stdout.writeAll("Digite seu nome: ");

    // Ler uma linha (até \n, máximo 256 bytes)
    var buffer: [256]u8 = undefined;
    const linha = try stdin.readUntilDelimiterOrEof(&buffer, '\n');

    if (linha) |nome| {
        try stdout.print("Olá, {s}!\n", .{nome});
    }
}

Stderr para erros e debug

const std = @import("std");

pub fn main() !void {
    const stderr = std.io.getStdErr().writer();
    try stderr.print("ERRO: {s}\n", .{"arquivo não encontrado"});

    // Ou usar std.debug.print (sempre vai para stderr)
    std.debug.print("Debug: valor = {d}\n", .{42});
}

Operações com Arquivos

Ler arquivo inteiro

const std = @import("std");

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

    // Ler arquivo inteiro para string
    const conteudo = try std.fs.cwd().readFileAlloc(
        allocator,
        "dados.txt",
        1024 * 1024, // máximo 1MB
    );
    defer allocator.free(conteudo);

    std.debug.print("Conteúdo: {s}\n", .{conteudo});
}

Ler arquivo linha por linha

const std = @import("std");

pub fn main() !void {
    const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
    defer arquivo.close();

    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var linha_buf: [4096]u8 = undefined;
    var numero_linha: usize = 1;

    while (try reader.readUntilDelimiterOrEof(&linha_buf, '\n')) |linha| {
        std.debug.print("{d}: {s}\n", .{ numero_linha, linha });
        numero_linha += 1;
    }
}

Escrever em arquivo

const std = @import("std");

pub fn main() !void {
    // Criar/sobrescrever arquivo
    const arquivo = try std.fs.cwd().createFile("saida.txt", .{});
    defer arquivo.close();

    const writer = arquivo.writer();

    try writer.writeAll("Primeira linha\n");
    try writer.print("Valor: {d}\n", .{42});
    try writer.print("Data: {s}\n", .{"2026-02-21"});
}

Append em arquivo existente

const std = @import("std");

pub fn main() !void {
    const arquivo = try std.fs.cwd().openFile("log.txt", .{
        .mode = .write_only,
    });
    defer arquivo.close();

    // Ir para o final do arquivo
    try arquivo.seekFromEnd(0);

    const writer = arquivo.writer();
    try writer.print("[INFO] Nova entrada de log\n", .{});
}

Buffered I/O

Buffers melhoram a performance reduzindo chamadas ao sistema:

const std = @import("std");

pub fn main() !void {
    const arquivo = try std.fs.cwd().createFile("grande.txt", .{});
    defer arquivo.close();

    // Writer com buffer — muito mais rápido para muitas escritas pequenas
    var buf_writer = std.io.bufferedWriter(arquivo.writer());
    const writer = buf_writer.writer();

    for (0..10000) |i| {
        try writer.print("Linha {d}\n", .{i});
    }

    // IMPORTANTE: flush para garantir que tudo foi escrito
    try buf_writer.flush();
}

Operações com Diretórios

const std = @import("std");

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

    // Diretório atual
    const cwd = std.fs.cwd();

    // Criar diretório
    cwd.makeDir("novo_dir") catch |err| switch (err) {
        error.PathAlreadyExists => {},
        else => return err,
    };

    // Criar diretórios recursivamente
    try cwd.makePath("a/b/c/d");

    // Listar conteúdo de diretório
    var dir = try cwd.openDir(".", .{ .iterate = true });
    defer dir.close();

    var iter = dir.iterate();
    while (try iter.next()) |entrada| {
        const tipo = switch (entrada.kind) {
            .file => "arquivo",
            .directory => "diretório",
            .sym_link => "link simbólico",
            else => "outro",
        };
        std.debug.print("{s}: {s}\n", .{ tipo, entrada.name });
    }

    // Caminhos absolutos
    var path_buf: [std.fs.max_path_bytes]u8 = undefined;
    const caminho_abs = try cwd.realpath(".", &path_buf);
    std.debug.print("Diretório atual: {s}\n", .{caminho_abs});

    // Deletar arquivo
    cwd.deleteFile("temp.txt") catch {};

    // Deletar diretório vazio
    cwd.deleteDir("novo_dir") catch {};

    // Deletar diretório recursivamente
    cwd.deleteTree("a") catch {};

    // Renomear
    try cwd.rename("antigo.txt", "novo.txt");

    // Verificar se existe
    _ = cwd.statFile("arquivo.txt") catch |err| {
        if (err == error.FileNotFound) {
            std.debug.print("Arquivo não existe\n", .{});
        }
        _ = allocator; // apenas para evitar unused
    };
}

Caminhos (Paths)

const std = @import("std");

pub fn main() void {
    const caminho = "/home/usuario/projeto/src/main.zig";

    // Basename — nome do arquivo
    const nome = std.fs.path.basename(caminho);
    std.debug.print("Nome: {s}\n", .{nome}); // main.zig

    // Dirname — diretório pai
    const dir = std.fs.path.dirname(caminho);
    std.debug.print("Dir: {s}\n", .{dir orelse "(nulo)"}); // /home/usuario/projeto/src

    // Extensão
    const ext = std.fs.path.extension(caminho);
    std.debug.print("Ext: {s}\n", .{ext}); // .zig

    // Stem — nome sem extensão
    const stem = std.fs.path.stem(caminho);
    std.debug.print("Stem: {s}\n", .{stem}); // main
}

Tabela de Referência — std.fs

OperaçãoMétodoExemplo
Abrir arquivoopenFile(path, flags)try cwd.openFile("a.txt", .{})
Criar arquivocreateFile(path, flags)try cwd.createFile("a.txt", .{})
Ler tudoreadFileAlloc(alloc, path, max)try cwd.readFileAlloc(alloc, "a.txt", 1e6)
Deletar arquivodeleteFile(path)try cwd.deleteFile("a.txt")
Criar diretóriomakeDir(path)try cwd.makeDir("dir")
Criar dirs recursivomakePath(path)try cwd.makePath("a/b/c")
Deletar diretóriodeleteDir(path)try cwd.deleteDir("dir")
Renomearrename(old, new)try cwd.rename("a", "b")
StatstatFile(path)try cwd.statFile("a.txt")

Tabela de Referência — Reader/Writer

ReaderDescrição
readAll(buf)Ler até encher o buffer
readUntilDelimiterOrEof(buf, delim)Ler até delimitador ou EOF
readByte()Ler um byte
readInt(T, endian)Ler inteiro com endianness
skipBytes(n, .{})Pular N bytes
WriterDescrição
writeAll(bytes)Escrever todos os bytes
print(fmt, args)Escrever formatado
writeByte(byte)Escrever um byte
writeInt(T, val, endian)Escrever inteiro com endianness

Erros Comuns

// ERRO: Esquecer de fechar arquivo
// const f = try cwd.openFile("x.txt", .{});
// ... sem close()

// CORRETO: Sempre usar defer
const f = try std.fs.cwd().openFile("x.txt", .{});
defer f.close();

// ERRO: Esquecer flush do buffer
// buf_writer escreveu mas não fez flush

// CORRETO: Sempre fazer flush
try buf_writer.flush();

// ERRO: Buffer muito pequeno para readUntilDelimiterOrEof
// var buf: [10]u8 = undefined; // pode estourar
// CORRETO: buffer generoso ou usar allocator

Veja Também

Continue aprendendo Zig

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