---
title: "File I/O em Zig: Leitura e Escrita de Arquivos"
url: "https://ziglang.com.br/tutoriais/zig-file-io/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-file-io.MD"
description: "Tutorial completo sobre File I/O em Zig. Aprenda a ler, escrever e manipular arquivos e diretórios com std.fs, buffered readers e tratamento de erros."
date: "2026-02-20"
author: "Zig Brasil"
---

# File I/O em Zig: Leitura e Escrita de Arquivos

Tutorial completo sobre File I/O em Zig. Aprenda a ler, escrever e manipular arquivos e diretórios com std.fs, buffered readers e tratamento de erros.


Operações de entrada e saída com arquivos estão entre as tarefas mais fundamentais em qualquer linguagem de programação de sistemas. Em **Zig lang**, a biblioteca padrão oferece uma API poderosa e segura para trabalhar com o sistema de arquivos através do módulo `std.fs`. Ao contrário de C, onde `fopen` e `fread` exigem cuidado constante com ponteiros e buffers, a **linguagem Zig** fornece abstrações que previnem erros comuns sem sacrificar o controle de baixo nível.

Neste tutorial, vamos explorar todas as operações essenciais de File I/O em Zig: desde a leitura e escrita básica de arquivos até navegação em diretórios, manipulação de caminhos e um exemplo prático completo de parser de logs.

## Abrindo e Fechando Arquivos com std.fs

O ponto de partida para qualquer operação de arquivo em Zig é o módulo `std.fs`. A abertura de arquivos retorna um `std.fs.File`, e a prática idiomática é usar `defer` para garantir que o arquivo seja fechado ao sair do escopo.

```zig
const std = @import("std");

pub fn main() !void {
    // Abrir um arquivo existente para leitura
    const file = try std.fs.cwd().openFile("dados.txt", .{});
    defer file.close();

    // O arquivo está pronto para leitura
    // defer garante que file.close() será chamado
    // mesmo se ocorrer um erro nas operações seguintes

    const stat = try file.stat();
    std.debug.print("Tamanho: {} bytes\n", .{stat.size});
    std.debug.print("Última modificação: {}\n", .{stat.mtime});
}
```

As opções de abertura permitem controle fino sobre o modo de acesso:

```zig
// Abrir para leitura e escrita
const file_rw = try std.fs.cwd().openFile("config.txt", .{
    .mode = .read_write,
});
defer file_rw.close();

// Abrir em um caminho absoluto
const abs_file = try std.fs.openFileAbsolute("/etc/hostname", .{});
defer abs_file.close();
```

## Leitura de Arquivos: readToEndAlloc e Buffered Reader

Zig oferece duas abordagens principais para leitura: carregar o arquivo inteiro na memória ou ler linha por linha com um buffered reader.

### Leitura Completa com readToEndAlloc

Para arquivos de tamanho moderado, `readToEndAlloc` é a forma mais direta (o conteudo retornado e um [slice de bytes](/tutoriais/strings-e-arrays-zig/)):

```zig
const std = @import("std");

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

    const file = try std.fs.cwd().openFile("poema.txt", .{});
    defer file.close();

    // Lê o arquivo inteiro — máximo de 10 MB
    const conteudo = try file.readToEndAlloc(allocator, 10 * 1024 * 1024);
    defer allocator.free(conteudo);

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

### Leitura Linha por Linha com Buffered Reader

Para arquivos grandes ou quando você precisa processar uma linha por vez, o buffered reader é mais eficiente em memória:

```zig
const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("access.log", .{});
    defer file.close();

    // Criar um buffered reader para leitura eficiente
    var buf_reader = std.io.bufferedReader(file.reader());
    const reader = buf_reader.reader();

    var line_buf: [4096]u8 = undefined;
    var line_count: usize = 0;

    // Ler linha por linha
    while (reader.readUntilDelimiterOrEof(&line_buf, '\n')) |line| {
        if (line) |data| {
            line_count += 1;
            std.debug.print("Linha {}: {s}\n", .{ line_count, data });
        } else {
            break; // EOF
        }
    } else |err| {
        std.debug.print("Erro de leitura: {}\n", .{err});
    }

    std.debug.print("\nTotal: {} linhas\n", .{line_count});
}
```

### Leitura com Alocação por Linha

Quando as linhas podem ter tamanho variável e você precisa armazená-las:

```zig
const std = @import("std");

pub fn lerLinhas(allocator: std.mem.Allocator, path: []const u8) !std.ArrayList([]u8) {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();

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

    var linhas = std.ArrayList([]u8).init(allocator);
    errdefer {
        for (linhas.items) |linha| allocator.free(linha);
        linhas.deinit();
    }

    while (true) {
        const linha = reader.readUntilDelimiterAlloc(allocator, '\n', 8192) catch |err| switch (err) {
            error.EndOfStream => break,
            else => return err,
        };
        try linhas.append(linha);
    }

    return linhas;
}
```

## Escrita de Arquivos: createFile e Writer Interface

Para criar ou sobrescrever arquivos, usamos `createFile`. Para adicionar conteúdo ao final, abrimos com `.mode = .read_write` e fazemos seek.

```zig
const std = @import("std");

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

    const writer = file.writer();

    // Escrita formatada — similar a printf
    try writer.print("Relatório gerado em: {}\n", .{std.time.timestamp()});
    try writer.print("Versão: {s}\n", .{"1.0.0"});

    // Escrever bytes diretamente
    try writer.writeAll("---\nDados do sistema:\n");

    // Escrever múltiplas vezes em um loop
    for (0..5) |i| {
        try writer.print("  Item {}: valor_{}\n", .{ i + 1, i * 10 });
    }

    std.debug.print("Arquivo 'saida.txt' criado com sucesso!\n", .{});
}
```

### Buffered Writer para Performance

Quando você faz muitas escritas pequenas, o buffered writer reduz as chamadas de sistema:

```zig
const std = @import("std");

pub fn gerarCSV(path: []const u8, dados: []const [3][]const u8) !void {
    const file = try std.fs.cwd().createFile(path, .{});
    defer file.close();

    // Buffered writer acumula dados antes de escrever no disco
    var buf_writer = std.io.bufferedWriter(file.writer());
    const writer = buf_writer.writer();

    // Cabeçalho
    try writer.writeAll("nome,idade,cidade\n");

    // Dados
    for (dados) |row| {
        try writer.print("{s},{s},{s}\n", .{ row[0], row[1], row[2] });
    }

    // IMPORTANTE: flush() envia os dados pendentes para o disco
    try buf_writer.flush();
}

pub fn main() !void {
    const dados = [_][3][]const u8{
        .{ "Ana", "28", "São Paulo" },
        .{ "Carlos", "35", "Rio de Janeiro" },
        .{ "Maria", "42", "Belo Horizonte" },
    };

    try gerarCSV("usuarios.csv", &dados);
    std.debug.print("CSV gerado com sucesso!\n", .{});
}
```

## Metadados de Arquivos: stat, Permissões e Timestamps

O método `stat()` retorna informações detalhadas sobre um arquivo:

```zig
const std = @import("std");

pub fn infoArquivo(path: []const u8) !void {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();

    const stat = try file.stat();

    std.debug.print("Arquivo: {s}\n", .{path});
    std.debug.print("  Tamanho: {} bytes\n", .{stat.size});
    std.debug.print("  Tipo: {}\n", .{stat.kind});

    // Timestamps em nanosegundos desde epoch
    const mtime_s = @divFloor(stat.mtime, std.time.ns_per_s);
    std.debug.print("  Modificado: {} (epoch seconds)\n", .{mtime_s});

    // Verificar permissões (em sistemas POSIX)
    const mode = stat.mode;
    const owner_read = (mode & 0o400) != 0;
    const owner_write = (mode & 0o200) != 0;
    const owner_exec = (mode & 0o100) != 0;
    std.debug.print("  Permissões do dono: {s}{s}{s}\n", .{
        if (owner_read) "r" else "-",
        if (owner_write) "w" else "-",
        if (owner_exec) "x" else "-",
    });
}
```

## Operações com Diretórios

Zig fornece APIs completas para criar, listar e percorrer diretórios.

### Listando Arquivos em um Diretório

```zig
const std = @import("std");

pub fn listarDiretorio(path: []const u8) !void {
    var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
    defer dir.close();

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

    var iter = dir.iterate();
    while (try iter.next()) |entry| {
        const tipo = switch (entry.kind) {
            .file => "ARQ",
            .directory => "DIR",
            .sym_link => "LNK",
            else => "???",
        };
        std.debug.print("  [{s}] {s}\n", .{ tipo, entry.name });
    }
}

pub fn main() !void {
    try listarDiretorio(".");
}
```

### Criando Diretórios e Percorrendo Árvores

```zig
const std = @import("std");

pub fn criarEstrutura(allocator: std.mem.Allocator) !void {
    const cwd = std.fs.cwd();

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

    // Criar diretórios aninhados (equivalente a mkdir -p)
    try cwd.makePath("projeto/src/utils");
    try cwd.makePath("projeto/tests");
    try cwd.makePath("projeto/docs");

    std.debug.print("Estrutura criada!\n", .{});

    // Percorrer a árvore recursivamente
    var walker = try cwd.openDir("projeto", .{ .iterate = true });
    defer walker.close();

    var iter = walker.iterate();
    while (try iter.next()) |entry| {
        std.debug.print("  {s} ({s})\n", .{
            entry.name,
            @tagName(entry.kind),
        });
    }

    _ = allocator;
}
```

## Manipulação de Caminhos com std.fs.path

O módulo `std.fs.path` oferece funções para manipular caminhos de forma portável:

```zig
const std = @import("std");

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

    // Extrair componentes do caminho
    std.debug.print("Diretório: {s}\n", .{std.fs.path.dirname(caminho) orelse "(nenhum)"});
    std.debug.print("Nome do arquivo: {s}\n", .{std.fs.path.basename(caminho)});
    std.debug.print("Extensão: {s}\n", .{std.fs.path.extension(caminho)});

    // Verificar se é absoluto
    std.debug.print("É absoluto: {}\n", .{std.fs.path.isAbsolute(caminho)});

    // Juntar caminhos
    const base = "/home/usuario";
    const relativo = "documentos/notas.txt";
    var buf: [std.fs.max_path_bytes]u8 = undefined;
    const junto = std.fmt.bufPrint(&buf, "{s}/{s}", .{ base, relativo }) catch unreachable;
    std.debug.print("Caminho completo: {s}\n", .{junto});
}
```

## Exemplo Prático: Parser de Arquivo de Log

Vamos construir um parser completo que lê um arquivo de log, extrai informações e gera um relatório:

```zig
const std = @import("std");

const LogEntry = struct {
    level: Level,
    message: []const u8,
    count: usize,

    const Level = enum { INFO, WARN, ERROR, DEBUG };
};

const LogStats = struct {
    total: usize = 0,
    info_count: usize = 0,
    warn_count: usize = 0,
    error_count: usize = 0,
    debug_count: usize = 0,
    error_messages: std.ArrayList([]const u8),

    pub fn init(allocator: std.mem.Allocator) LogStats {
        return .{
            .error_messages = std.ArrayList([]const u8).init(allocator),
        };
    }

    pub fn deinit(self: *LogStats) void {
        self.error_messages.deinit();
    }

    pub fn registrar(self: *LogStats, line: []const u8) !void {
        self.total += 1;

        if (std.mem.startsWith(u8, line, "[INFO]")) {
            self.info_count += 1;
        } else if (std.mem.startsWith(u8, line, "[WARN]")) {
            self.warn_count += 1;
        } else if (std.mem.startsWith(u8, line, "[ERROR]")) {
            self.error_count += 1;
            try self.error_messages.append(line);
        } else if (std.mem.startsWith(u8, line, "[DEBUG]")) {
            self.debug_count += 1;
        }
    }
};

pub fn parseLog(allocator: std.mem.Allocator, input_path: []const u8, output_path: []const u8) !void {
    // Abrir arquivo de entrada
    const input = try std.fs.cwd().openFile(input_path, .{});
    defer input.close();

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

    var stats = LogStats.init(allocator);
    defer stats.deinit();

    // Processar cada linha
    var line_buf: [8192]u8 = undefined;
    while (reader.readUntilDelimiterOrEof(&line_buf, '\n')) |maybe_line| {
        const line = maybe_line orelse break;
        if (line.len == 0) continue;
        try stats.registrar(line);
    } else |err| {
        return err;
    }

    // Gerar relatório
    const output = try std.fs.cwd().createFile(output_path, .{});
    defer output.close();

    var buf_writer = std.io.bufferedWriter(output.writer());
    const writer = buf_writer.writer();

    try writer.writeAll("=== RELATÓRIO DE LOG ===\n\n");
    try writer.print("Total de linhas: {}\n", .{stats.total});
    try writer.print("INFO:  {} ({d:.1}%)\n", .{
        stats.info_count,
        percentual(stats.info_count, stats.total),
    });
    try writer.print("WARN:  {} ({d:.1}%)\n", .{
        stats.warn_count,
        percentual(stats.warn_count, stats.total),
    });
    try writer.print("ERROR: {} ({d:.1}%)\n", .{
        stats.error_count,
        percentual(stats.error_count, stats.total),
    });
    try writer.print("DEBUG: {} ({d:.1}%)\n", .{
        stats.debug_count,
        percentual(stats.debug_count, stats.total),
    });

    if (stats.error_messages.items.len > 0) {
        try writer.writeAll("\n--- ERROS ENCONTRADOS ---\n");
        for (stats.error_messages.items) |msg| {
            try writer.print("  {s}\n", .{msg});
        }
    }

    try buf_writer.flush();
    std.debug.print("Relatório salvo em '{s}'\n", .{output_path});
}

fn percentual(parte: usize, total: usize) f64 {
    if (total == 0) return 0.0;
    return @as(f64, @floatFromInt(parte)) / @as(f64, @floatFromInt(total)) * 100.0;
}

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

    try parseLog(gpa.allocator(), "app.log", "relatorio.txt");
}
```

## Tratamento de Erros em Operações de Arquivo

Operações de I/O são inerentemente sujeitas a falhas. Zig torna o [tratamento de erros](/tutoriais/tratamento-de-erros-em-zig/) explícito e composível:

```zig
const std = @import("std");

pub fn lerArquivoSeguro(path: []const u8) ![]const u8 {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const file = std.fs.cwd().openFile(path, .{}) catch |err| {
        switch (err) {
            error.FileNotFound => {
                std.debug.print("Arquivo '{s}' não encontrado.\n", .{path});
                return err;
            },
            error.AccessDenied => {
                std.debug.print("Sem permissão para ler '{s}'.\n", .{path});
                return err;
            },
            else => {
                std.debug.print("Erro ao abrir '{s}': {}\n", .{ path, err });
                return err;
            },
        }
    };
    defer file.close();

    const conteudo = file.readToEndAlloc(allocator, 1024 * 1024) catch |err| {
        switch (err) {
            error.OutOfMemory => {
                std.debug.print("Arquivo muito grande para ler na memória.\n", .{});
                return err;
            },
            else => return err,
        }
    };

    return conteudo;
}

pub fn copiarArquivo(origem: []const u8, destino: []const u8) !void {
    // Abrir origem
    const src = try std.fs.cwd().openFile(origem, .{});
    defer src.close();

    // Criar destino
    const dst = try std.fs.cwd().createFile(destino, .{});
    errdefer {
        dst.close();
        // Se falhar, remover arquivo parcial
        std.fs.cwd().deleteFile(destino) catch {};
    }
    defer dst.close();

    // Copiar em blocos
    var buf: [4096]u8 = undefined;
    while (true) {
        const bytes_read = try src.read(&buf);
        if (bytes_read == 0) break;
        try dst.writeAll(buf[0..bytes_read]);
    }

    std.debug.print("Copiado: {s} -> {s}\n", .{ origem, destino });
}
```

Note o uso de `errdefer` para limpar o arquivo de destino parcial em caso de falha durante a cópia. Esse padrão garante que operações incompletas não deixem arquivos corrompidos no sistema.

## Conclusão

O módulo `std.fs` do Zig oferece uma API completa e segura para operações de arquivo. Os pontos principais são: use `defer` para garantir que arquivos sejam fechados, prefira buffered readers/writers para performance, trate cada erro de forma específica e utilize `errdefer` para limpeza em caso de falha. Com essas ferramentas, você pode construir desde scripts simples de processamento de texto até sistemas complexos de gerenciamento de arquivos com total controle e segurança.

## Leia Também

- [CLI em Zig: Construindo Aplicações de Linha de Comando](/tutoriais/cli-em-zig/)
- [Tratamento de Erros em Zig](/tutoriais/tratamento-de-erros-em-zig/)
- [Strings e Arrays em Zig](/tutoriais/strings-e-arrays-zig/)
