---
title: "Zig para Programadores Rust: Comparação e Guia Prático"
url: "https://ziglang.com.br/tutoriais/zig-para-programadores-rust/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-para-programadores-rust.MD"
description: "Guia completo de Zig para quem já programa em Rust. Compare ownership vs allocators, traits vs comptime, cargo vs zig build, e veja código lado a lado."
date: "2026-02-24"
author: ""
---

# Zig para Programadores Rust: Comparação e Guia Prático

Guia completo de Zig para quem já programa em Rust. Compare ownership vs allocators, traits vs comptime, cargo vs zig build, e veja código lado a lado.


Se você é um programador Rust experiente e está curioso sobre **[Zig](/tutoriais/o-que-e-zig/)**, este guia é para você. Vamos explorar, lado a lado, como os conceitos que você já domina em Rust se traduzem para Zig — e entender por que tantos desenvolvedores de sistemas estão adotando essa linguagem como complemento ou alternativa ao Rust. Se você também busca conteúdo sobre Rust em português, confira o [Rust Lang Brasil](https://rustlang.com.br).

## Introdução: Por que Programadores Rust se Interessam por Zig?

Rust revolucionou a programação de sistemas com seu *borrow checker* e garantias de segurança em tempo de compilação. Então por que um programador Rust deveria conhecer Zig?

A resposta está na **filosofia de simplicidade**. Zig resolve muitos dos mesmos problemas que Rust, mas com uma abordagem radicalmente diferente:

- **Sem borrow checker, sem lifetimes**: Zig confia no programador e fornece ferramentas de detecção em runtime (em modo debug) em vez de um sistema de tipos complexo.
- **Sem macros procedurais**: `comptime` substitui todo o sistema de macros de Rust com código Zig real, depurável.
- **Sem traits nem generics tradicionais**: Polimorfismo é feito via `comptime` e interfaces baseadas em convenção.
- **[Interop com C nativa](/tutoriais/zig-c-interoperabilidade/)**: Importe headers C diretamente, sem `bindgen` ou `unsafe` blocks.
- **[Build system integrado](/tutoriais/zig-build-system/)**: Sem Cargo.toml, sem TOML, sem dependências externas. O `build.zig` é escrito na própria linguagem.
- **Binários pequenos e previsíveis**: Sem runtime, sem unwinding de exceções, controle total sobre alocações.

Aqui está uma visão geral das diferenças fundamentais:

| Característica | Rust | Zig |
|---|---|---|
| **Segurança de memória** | Borrow checker (compilação) | Allocators explícitos + detecção em debug |
| **Tratamento de erros** | `Result<T, E>` e `?` | Error unions (`!T`) e `try`/`catch` |
| **Generics** | Traits + type parameters | `comptime` + duck typing |
| **Macros** | `macro_rules!` e proc macros | `comptime` (código real) |
| **Build system** | Cargo | `build.zig` integrado |
| **Async** | `async`/`.await` + runtime (Tokio) | io_uring nativo (sem runtime) |
| **Strings** | `String` / `&str` | `[]const u8` (slices) |
| **Null safety** | `Option<T>` | Tipos opcionais (`?T`) |
| **FFI com C** | `extern "C"` + `unsafe` | `@cImport` nativo, sem `unsafe` |
| **Gerenciamento de pacotes** | crates.io | Zig Package Manager (build.zig.zon) |

## Modelo de Memória: Ownership/Borrow Checker vs Allocators Explícitos

Esta talvez seja a maior diferença conceitual entre as duas linguagens. Em Rust, o compilador rastreia a propriedade (*ownership*) de cada valor e garante que referências sejam sempre válidas. Em Zig, o programador gerencia a memória explicitamente usando *allocators*.

### Ownership e Move Semantics

**Rust** — O valor é movido e a variável original se torna inválida:

```rust
fn main() {
    let nome = String::from("Zig Brasil");
    let outro = nome; // move ocorre aqui
    // println!("{}", nome); // ERRO: nome foi movido
    println!("{}", outro);
}
```

**Zig** — Não existe conceito de "move". O programador controla a alocação:

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

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

    const nome = try std.fmt.allocPrint(allocator, "Zig Brasil", .{});
    defer allocator.free(nome);

    // Copiar o slice é apenas copiar o ponteiro e o tamanho (não há "move")
    const outro = nome;

    std.debug.print("{s}\n", .{outro});
    std.debug.print("{s}\n", .{nome}); // Perfeitamente válido
}
```

### Borrowing vs Slices

**Rust** — O borrow checker garante que referências mutáveis sejam exclusivas:

```rust
fn processar(dados: &mut Vec<i32>) {
    dados.push(42);
}

fn visualizar(dados: &[i32]) {
    for item in dados {
        println!("{}", item);
    }
}

fn main() {
    let mut lista = vec![1, 2, 3];
    processar(&mut lista);
    visualizar(&lista);
}
```

**Zig** — Slices são passados explicitamente, sem borrow checker:

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

fn processar(lista: *std.ArrayList(i32)) !void {
    try lista.append(42);
}

fn visualizar(dados: []const i32) void {
    for (dados) |item| {
        std.debug.print("{d}\n", .{item});
    }
}

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

    var lista = std.ArrayList(i32).init(allocator);
    defer lista.deinit();

    try lista.appendSlice(&.{ 1, 2, 3 });
    try processar(&lista);
    visualizar(lista.items);
}
```

### Lifetimes vs defer/errdefer

**Rust** — Lifetimes anotam quanto tempo uma referência vive:

```rust
struct Parser<'a> {
    input: &'a str,
    posicao: usize,
}

impl<'a> Parser<'a> {
    fn new(input: &'a str) -> Self {
        Parser { input, posicao: 0 }
    }

    fn proximo_char(&mut self) -> Option<char> {
        let ch = self.input[self.posicao..].chars().next()?;
        self.posicao += ch.len_utf8();
        Some(ch)
    }
}
```

**Zig** — `defer` e `errdefer` garantem limpeza determinística:

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

const Parser = struct {
    input: []const u8,
    posicao: usize,

    pub fn init(input: []const u8) Parser {
        return .{
            .input = input,
            .posicao = 0,
        };
    }

    pub fn proximoChar(self: *Parser) ?u8 {
        if (self.posicao >= self.input.len) return null;
        const ch = self.input[self.posicao];
        self.posicao += 1;
        return ch;
    }
};

pub fn main() void {
    var parser = Parser.init("Olá Zig");
    while (parser.proximoChar()) |ch| {
        std.debug.print("{c}", .{ch});
    }
    std.debug.print("\n", .{});
}
```

### Arena Allocator: Padrão Comum em Zig

Em Rust, o borrow checker lida com a maioria dos casos de vida útil de memória. Em Zig, o padrão `ArenaAllocator` é extremamente comum para agrupar alocações e liberá-las de uma só vez:

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

fn processarDocumento(allocator: std.mem.Allocator, conteudo: []const u8) ![]const u8 {
    // Todas as alocações temporárias usam o arena
    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit(); // Libera TUDO de uma vez

    const arena_alloc = arena.allocator();

    // Alocações temporárias — não precisam de free individual
    const linhas = try splitLinhas(arena_alloc, conteudo);
    const processadas = try processarLinhas(arena_alloc, linhas);

    // Apenas o resultado final é alocado no allocator externo
    return try std.fmt.allocPrint(allocator, "{s}", .{processadas});
}

fn splitLinhas(allocator: std.mem.Allocator, texto: []const u8) ![][]const u8 {
    var lista = std.ArrayList([]const u8).init(allocator);
    var iter = std.mem.splitScalar(u8, texto, '\n');
    while (iter.next()) |linha| {
        try lista.append(linha);
    }
    return lista.toOwnedSlice();
}

fn processarLinhas(allocator: std.mem.Allocator, linhas: [][]const u8) ![]const u8 {
    var resultado = std.ArrayList(u8).init(allocator);
    for (linhas) |linha| {
        try resultado.appendSlice(linha);
        try resultado.append('\n');
    }
    return resultado.toOwnedSlice();
}
```

## Tratamento de Erros: Result<T,E> vs Error Unions

Ambas as linguagens tratam erros como valores (sem exceções), mas a sintaxe e os mecanismos são distintos.

### Padrão Básico

**Rust:**

```rust
use std::fs;
use std::io;

fn ler_config(caminho: &str) -> Result<String, io::Error> {
    let conteudo = fs::read_to_string(caminho)?;
    Ok(conteudo.trim().to_string())
}

fn main() {
    match ler_config("config.toml") {
        Ok(config) => println!("Config: {}", config),
        Err(e) => eprintln!("Erro: {}", e),
    }
}
```

**Zig:**

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

fn lerConfig(allocator: std.mem.Allocator, caminho: []const u8) ![]const u8 {
    const arquivo = std.fs.cwd().openFile(caminho, .{}) catch |err| {
        std.log.err("Não foi possível abrir {s}: {}", .{ caminho, err });
        return err;
    };
    defer arquivo.close();

    return try arquivo.readToEndAlloc(allocator, 1024 * 1024);
}

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

    const config = lerConfig(allocator, "config.toml") catch |err| {
        std.debug.print("Erro: {}\n", .{err});
        return;
    };
    defer allocator.free(config);

    std.debug.print("Config: {s}\n", .{config});
}
```

### Definindo Erros Personalizados

**Rust:**

```rust
#[derive(Debug)]
enum MeuErro {
    ArquivoNaoEncontrado,
    FormatoInvalido(String),
    SemPermissao,
}

impl std::fmt::Display for MeuErro {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            MeuErro::ArquivoNaoEncontrado => write!(f, "Arquivo não encontrado"),
            MeuErro::FormatoInvalido(msg) => write!(f, "Formato inválido: {}", msg),
            MeuErro::SemPermissao => write!(f, "Sem permissão"),
        }
    }
}

impl std::error::Error for MeuErro {}
```

**Zig:**

```zig
const MeuErro = error{
    ArquivoNaoEncontrado,
    FormatoInvalido,
    SemPermissao,
};

fn operacao() MeuErro!u32 {
    return MeuErro.FormatoInvalido;
}

fn operacaoComDetalhes() MeuErro!u32 {
    // Em Zig, erros são valores simples. Para detalhes extras,
    // use logging ou retorne informação adicional via parâmetro.
    std.log.err("Formato inválido: campo 'nome' ausente", .{});
    return MeuErro.FormatoInvalido;
}
```

### errdefer: O Padrão que Rust Não Tem

Um dos recursos mais elegantes de Zig é `errdefer`, que executa código de limpeza apenas quando a função retorna um erro:

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

const Conexao = struct {
    socket: std.posix.socket_t,
    buffer: []u8,

    pub fn init(allocator: std.mem.Allocator, endereco: []const u8) !Conexao {
        const socket = try std.posix.socket(
            std.posix.AF.INET,
            std.posix.SOCK.STREAM,
            0,
        );
        errdefer std.posix.close(socket); // Fecha socket se algo falhar abaixo

        const buffer = try allocator.alloc(u8, 4096);
        errdefer allocator.free(buffer); // Libera buffer se algo falhar abaixo

        // Se a conexão falhar, socket e buffer são liberados automaticamente
        try conectar(socket, endereco);

        return .{
            .socket = socket,
            .buffer = buffer,
        };
    }

    pub fn deinit(self: *Conexao, allocator: std.mem.Allocator) void {
        std.posix.close(self.socket);
        allocator.free(self.buffer);
    }
};

fn conectar(socket: std.posix.socket_t, endereco: []const u8) !void {
    _ = socket;
    _ = endereco;
    // Implementação da conexão...
}
```

Em Rust, esse padrão requer `Drop` traits ou uso manual de `guard` patterns. O `errdefer` de Zig é mais explícito e direto.

## Generics: Traits vs Comptime

Esta é uma diferença filosófica profunda. Rust usa traits para polimorfismo parametrizado com verificação em tempo de compilação. Zig usa `comptime` com *duck typing* em tempo de compilação.

### Função Genérica

**Rust:**

```rust
use std::ops::Add;

fn somar<T: Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

fn main() {
    println!("{}", somar(10i32, 20i32));
    println!("{}", somar(1.5f64, 2.5f64));
}
```

**Zig:**

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

fn somar(comptime T: type, a: T, b: T) T {
    return a + b;
}

pub fn main() void {
    std.debug.print("{d}\n", .{somar(i32, 10, 20)});
    std.debug.print("{d}\n", .{somar(f64, 1.5, 2.5)});
}
```

### Traits vs Interfaces por Convenção

**Rust** — Traits definem interfaces formais:

```rust
trait Serializavel {
    fn serializar(&self) -> String;
    fn tamanho(&self) -> usize;
}

struct Usuario {
    nome: String,
    idade: u32,
}

impl Serializavel for Usuario {
    fn serializar(&self) -> String {
        format!("{}:{}", self.nome, self.idade)
    }

    fn tamanho(&self) -> usize {
        self.nome.len() + 4 // 4 bytes para idade
    }
}

fn processar<T: Serializavel>(item: &T) {
    println!("Dados: {}", item.serializar());
    println!("Tamanho: {} bytes", item.tamanho());
}
```

**Zig** — Duck typing em comptime (se o tipo tem os métodos necessários, funciona):

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

const Usuario = struct {
    nome: []const u8,
    idade: u32,

    pub fn serializar(self: Usuario, buffer: []u8) []u8 {
        return std.fmt.bufPrint(buffer, "{s}:{d}", .{ self.nome, self.idade }) catch buffer[0..0];
    }

    pub fn tamanho(self: Usuario) usize {
        return self.nome.len + 4;
    }
};

const Produto = struct {
    titulo: []const u8,
    preco: f64,

    pub fn serializar(self: Produto, buffer: []u8) []u8 {
        return std.fmt.bufPrint(buffer, "{s}:{d:.2}", .{ self.titulo, self.preco }) catch buffer[0..0];
    }

    pub fn tamanho(self: Produto) usize {
        return self.titulo.len + 8;
    }
};

fn processar(comptime T: type, item: T) void {
    var buffer: [256]u8 = undefined;
    const dados = item.serializar(&buffer);
    std.debug.print("Dados: {s}\n", .{dados});
    std.debug.print("Tamanho: {d} bytes\n", .{item.tamanho()});
}

pub fn main() void {
    const usuario = Usuario{ .nome = "Ana", .idade = 30 };
    const produto = Produto{ .titulo = "Teclado", .preco = 199.90 };

    processar(Usuario, usuario);
    processar(Produto, produto);
}
```

### Trait Objects vs Interfaces Dinâmicas (vtable)

**Rust** — `dyn Trait` para dispatch dinâmico:

```rust
trait Animal {
    fn som(&self) -> &str;
    fn nome(&self) -> &str;
}

struct Cachorro { nome: String }
struct Gato { nome: String }

impl Animal for Cachorro {
    fn som(&self) -> &str { "Au au!" }
    fn nome(&self) -> &str { &self.nome }
}

impl Animal for Gato {
    fn som(&self) -> &str { "Miau!" }
    fn nome(&self) -> &str { &self.nome }
}

fn apresentar(animal: &dyn Animal) {
    println!("{}: {}", animal.nome(), animal.som());
}
```

**Zig** — Usa ponteiros opacos e vtables manuais ou `std.mem.Allocator`-style interfaces:

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

const Animal = struct {
    ptr: *anyopaque,
    somFn: *const fn (*anyopaque) []const u8,
    nomeFn: *const fn (*anyopaque) []const u8,

    pub fn som(self: Animal) []const u8 {
        return self.somFn(self.ptr);
    }

    pub fn nome(self: Animal) []const u8 {
        return self.nomeFn(self.ptr);
    }

    pub fn init(comptime T: type, ptr: *T) Animal {
        return .{
            .ptr = @ptrCast(ptr),
            .somFn = @ptrCast(&T.som),
            .nomeFn = @ptrCast(&T.nome),
        };
    }
};

const Cachorro = struct {
    nome_animal: []const u8,

    pub fn som(_: *Cachorro) []const u8 {
        return "Au au!";
    }
    pub fn nome(self: *Cachorro) []const u8 {
        return self.nome_animal;
    }
};

const Gato = struct {
    nome_animal: []const u8,

    pub fn som(_: *Gato) []const u8 {
        return "Miau!";
    }
    pub fn nome(self: *Gato) []const u8 {
        return self.nome_animal;
    }
};

fn apresentar(animal: Animal) void {
    std.debug.print("{s}: {s}\n", .{ animal.nome(), animal.som() });
}

pub fn main() void {
    var cachorro = Cachorro{ .nome_animal = "Rex" };
    var gato = Gato{ .nome_animal = "Mimi" };

    apresentar(Animal.init(Cachorro, &cachorro));
    apresentar(Animal.init(Gato, &gato));
}
```

## Build System: Cargo vs Zig Build

Programadores Rust estão acostumados com o excelente Cargo. O [sistema de build do Zig](/tutoriais/zig-build-system/) é diferente, mas igualmente poderoso.

### Estrutura de Projeto

**Rust (Cargo):**

```
meu-projeto/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs
│   ├── lib.rs
│   └── utils/
│       └── mod.rs
└── tests/
    └── integration_test.rs
```

**Zig:**

```
meu-projeto/
├── build.zig
├── build.zig.zon
├── src/
│   ├── main.zig
│   └── utils.zig
└── test/
    └── integration_test.zig
```

### Arquivo de Configuração

**Rust — Cargo.toml:**

```toml
[package]
name = "meu-projeto"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

[dev-dependencies]
criterion = "0.5"
```

**Zig — build.zig:**

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

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "meu-projeto",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);

    // Testes unitários
    const testes = b.addTest(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    const rodar_testes = b.addRunArtifact(testes);
    const passo_teste = b.step("test", "Executar testes unitários");
    passo_teste.dependOn(&rodar_testes.step);

    // Passo para rodar o executável
    const rodar = b.addRunArtifact(exe);
    rodar.step.dependOn(b.getInstallStep());
    const passo_rodar = b.step("run", "Executar o programa");
    passo_rodar.dependOn(&rodar.step);
}
```

### Dependências Externas

**Zig — build.zig.zon:**

```zig
.{
    .name = "meu-projeto",
    .version = "0.1.0",
    .dependencies = .{
        .zap = .{
            .url = "https://github.com/zigzap/zap/archive/v0.1.0.tar.gz",
            .hash = "122013fd26b5cf2608da3905cf04e2b8eff9e74310ff8cfb15e41c1e83847e01e5",
        },
    },
    .paths = .{"."},
}
```

### Comandos Comparados

| Ação | Rust (Cargo) | Zig |
|---|---|---|
| Compilar | `cargo build` | `zig build` |
| Executar | `cargo run` | `zig build run` |
| Testar | `cargo test` | `zig build test` |
| Release | `cargo build --release` | `zig build -Doptimize=ReleaseFast` |
| Cross-compile | Precisa de toolchain | `zig build -Dtarget=aarch64-linux` |
| Formatar | `cargo fmt` | `zig fmt src/` |

## Async: Tokio vs io_uring

Esta é uma das maiores diferenças entre as duas linguagens. Rust depende de runtimes de terceiros (como Tokio) para async. Zig integra [suporte nativo a io_uring](/tutoriais/zig-async-iouring/) diretamente.

### Servidor HTTP Básico

**Rust com Tokio:**

```rust
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;
    println!("Servidor rodando em :8080");

    loop {
        let (mut socket, addr) = listener.accept().await?;
        println!("Conexão de: {}", addr);

        tokio::spawn(async move {
            let mut buf = [0u8; 1024];
            match socket.read(&mut buf).await {
                Ok(n) => {
                    let resposta = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nOlá";
                    let _ = socket.write_all(resposta.as_bytes()).await;
                    let _ = socket.shutdown().await;
                }
                Err(e) => eprintln!("Erro: {}", e),
            }
        });
    }
}
```

**Zig com std.http (síncrono, mas leve):**

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

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

    var server = try std.http.Server.init(allocator, .{});
    defer server.deinit();

    const endereco = try net.Address.parseIp("127.0.0.1", 8080);
    try server.listen(endereco);
    std.debug.print("Servidor rodando em :8080\n", .{});

    while (true) {
        var response = try server.accept(.{ .allocator = allocator });
        defer response.deinit();

        try response.headers.append("Content-Type", "text/plain");
        try response.do();

        try response.writeAll("Olá");
        try response.finish();
    }
}
```

### Async sem Runtime

A diferença fundamental entre Rust e Zig em relação a async:

| Aspecto | Rust | Zig |
|---|---|---|
| **Runtime** | Necessário (Tokio, async-std) | Nenhum (usa io_uring ou epoll) |
| **Modelo** | Futures + executor | Event loop no kernel |
| **Overhead** | Alocações para tasks | Zero-copy, sem alocações extras |
| **Ecossistema** | Divisão sync/async | Uma única API para tudo |
| **Coloração de funções** | Sim (`async fn` vs `fn`) | Não existe diferenciação |

Em Zig, operações de I/O podem ser feitas de forma eficiente usando threads do sistema operacional ou integrações diretas com io_uring no Linux, sem necessidade de um runtime separado como Tokio.

## Macros vs Comptime

Se você já escreveu uma proc macro em Rust, sabe a dor: crate separado, `syn`, `quote`, `TokenStream`, compilação lenta. Em Zig, `comptime` faz tudo isso com código Zig normal.

### Derive Macro vs Comptime Reflection

**Rust — Derive macro para serialização (simplificado):**

```rust
use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    host: String,
    porta: u16,
    debug: bool,
}

fn main() {
    let config = Config {
        host: "localhost".to_string(),
        porta: 8080,
        debug: true,
    };

    let json = serde_json::to_string_pretty(&config).unwrap();
    println!("{}", json);
}
```

**Zig — Reflexão com comptime para gerar JSON:**

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

const Config = struct {
    host: []const u8,
    porta: u16,
    debug: bool,
};

fn paraJson(comptime T: type, valor: T, writer: anytype) !void {
    const info = @typeInfo(T);
    switch (info) {
        .@"struct" => |s| {
            try writer.writeAll("{");
            inline for (s.fields, 0..) |field, i| {
                if (i > 0) try writer.writeAll(",");
                try writer.print("\"{s}\":", .{field.name});
                try paraJson(field.type, @field(valor, field.name), writer);
            }
            try writer.writeAll("}");
        },
        .pointer => |p| {
            if (p.size == .Slice and p.child == u8) {
                try writer.print("\"{s}\"", .{valor});
            }
        },
        .int => try writer.print("{d}", .{valor}),
        .bool => try writer.writeAll(if (valor) "true" else "false"),
        else => try writer.writeAll("null"),
    }
}

pub fn main() !void {
    const config = Config{
        .host = "localhost",
        .porta = 8080,
        .debug = true,
    };

    const stdout = std.io.getStdOut().writer();
    try paraJson(Config, config, stdout);
    try stdout.writeAll("\n");
}
```

O `comptime` de Zig itera sobre os campos da struct **em tempo de compilação**, gerando código especializado para cada tipo. Sem macros, sem crate separado, sem `TokenStream`.

### Macro para Logging vs Comptime

**Rust:**

```rust
macro_rules! log_debug {
    ($($arg:tt)*) => {
        if cfg!(debug_assertions) {
            eprintln!("[DEBUG {}:{}] {}", file!(), line!(), format!($($arg)*));
        }
    };
}

fn main() {
    log_debug!("Valor: {}", 42);
}
```

**Zig:**

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

fn logDebug(comptime fmt: []const u8, args: anytype) void {
    if (builtin.mode == .Debug) {
        std.debug.print("[DEBUG {s}:{d}] " ++ fmt ++ "\n", .{@src().file, @src().line} ++ args);
    }
}

pub fn main() void {
    logDebug("Valor: {d}", .{42});
}
```

### Geração de Código em Comptime

Um dos poderes mais impressionantes do `comptime` é gerar tipos e funções inteiramente em tempo de compilação:

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

fn CriarVetor(comptime T: type, comptime N: usize) type {
    return struct {
        dados: [N]T,

        const Self = @This();

        pub fn init(valor_padrao: T) Self {
            return .{ .dados = [_]T{valor_padrao} ** N };
        }

        pub fn get(self: Self, indice: usize) ?T {
            if (indice >= N) return null;
            return self.dados[indice];
        }

        pub fn set(self: *Self, indice: usize, valor: T) void {
            if (indice < N) {
                self.dados[indice] = valor;
            }
        }

        pub fn soma(self: Self) T {
            var total: T = 0;
            for (self.dados) |v| {
                total += v;
            }
            return total;
        }

        pub fn tamanho() usize {
            return N;
        }
    };
}

pub fn main() void {
    const Vec3f = CriarVetor(f32, 3);
    var v = Vec3f.init(0.0);
    v.set(0, 1.0);
    v.set(1, 2.0);
    v.set(2, 3.0);

    std.debug.print("Soma: {d}\n", .{v.soma()});
    std.debug.print("Tamanho: {d}\n", .{Vec3f.tamanho()});
}
```

Em Rust, esse tipo de geração de código exigiria macros procedurais complexas ou genéricos com bounds extensos.

## FFI e Interoperabilidade com C

Tanto Rust quanto Zig são excelentes para FFI com C, mas a abordagem é drasticamente diferente.

### Importando Funções C

**Rust:**

```rust
// Precisa de bindings manuais ou bindgen
extern "C" {
    fn strlen(s: *const std::os::raw::c_char) -> usize;
    fn printf(format: *const std::os::raw::c_char, ...) -> i32;
}

fn main() {
    let msg = std::ffi::CString::new("Olá do Rust!\n").unwrap();
    unsafe {
        printf(msg.as_ptr());
        let tamanho = strlen(msg.as_ptr());
        println!("Tamanho: {}", tamanho);
    }
}
```

**Zig:**

```zig
const std = @import("std");
const c = @cImport({
    @cInclude("string.h");
    @cInclude("stdio.h");
});

pub fn main() void {
    const msg = "Olá do Zig!\n";
    _ = c.printf(msg.ptr);

    const tamanho = c.strlen(msg.ptr);
    std.debug.print("Tamanho: {d}\n", .{tamanho});
}
```

Em Zig, `@cImport` lê headers C diretamente e gera bindings automaticamente. Não há `unsafe`, não há `CString`, não há `bindgen`.

### Usando uma Biblioteca C Completa

**Rust — Usando SQLite via rusqlite (crate com bindings):**

```rust
use rusqlite::{Connection, Result};

fn main() -> Result<()> {
    let conn = Connection::open_in_memory()?;

    conn.execute(
        "CREATE TABLE usuarios (id INTEGER PRIMARY KEY, nome TEXT NOT NULL)",
        [],
    )?;

    conn.execute("INSERT INTO usuarios (nome) VALUES (?1)", ["Ana"])?;

    let mut stmt = conn.prepare("SELECT id, nome FROM usuarios")?;
    let rows = stmt.query_map([], |row| {
        Ok((row.get::<_, i32>(0)?, row.get::<_, String>(1)?))
    })?;

    for row in rows {
        let (id, nome) = row?;
        println!("{}: {}", id, nome);
    }
    Ok(())
}
```

**Zig — Usando SQLite diretamente via @cImport:**

```zig
const std = @import("std");
const c = @cImport({
    @cInclude("sqlite3.h");
});

pub fn main() !void {
    var db: ?*c.sqlite3 = null;

    if (c.sqlite3_open(":memory:", &db) != c.SQLITE_OK) {
        std.debug.print("Erro ao abrir banco\n", .{});
        return error.DatabaseError;
    }
    defer _ = c.sqlite3_close(db);

    // Criar tabela
    var err_msg: [*c]u8 = null;
    _ = c.sqlite3_exec(
        db,
        "CREATE TABLE usuarios (id INTEGER PRIMARY KEY, nome TEXT NOT NULL)",
        null,
        null,
        &err_msg,
    );

    // Inserir dados
    _ = c.sqlite3_exec(
        db,
        "INSERT INTO usuarios (nome) VALUES ('Ana')",
        null,
        null,
        &err_msg,
    );

    // Consultar
    var stmt: ?*c.sqlite3_stmt = null;
    _ = c.sqlite3_prepare_v2(db, "SELECT id, nome FROM usuarios", -1, &stmt, null);

    while (c.sqlite3_step(stmt) == c.SQLITE_ROW) {
        const id = c.sqlite3_column_int(stmt, 0);
        const nome_ptr = c.sqlite3_column_text(stmt, 1);
        std.debug.print("{d}: {s}\n", .{ id, nome_ptr });
    }
    _ = c.sqlite3_finalize(stmt);
}
```

A vantagem de Zig aqui: você usa a API C diretamente, sem camada de abstração intermediária. Para o build, basta adicionar ao `build.zig`:

```zig
exe.linkSystemLibrary("sqlite3");
exe.linkLibC();
```

## Comparação de Padrões Comuns

### Iteradores

**Rust:**

```rust
fn main() {
    let numeros = vec![1, 2, 3, 4, 5];

    let resultado: Vec<i32> = numeros
        .iter()
        .filter(|&&x| x > 2)
        .map(|&x| x * x)
        .collect();

    println!("{:?}", resultado); // [9, 16, 25]
}
```

**Zig:**

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

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

    const numeros = [_]i32{ 1, 2, 3, 4, 5 };

    var resultado = std.ArrayList(i32).init(allocator);
    defer resultado.deinit();

    for (numeros) |x| {
        if (x > 2) {
            try resultado.append(x * x);
        }
    }

    for (resultado.items) |v| {
        std.debug.print("{d} ", .{v});
    }
    std.debug.print("\n", .{});
}
```

Zig não tem iteradores encadeáveis estilo funcional. Os loops `for` são explícitos e diretos.

### Enums com Dados

**Rust:**

```rust
enum Mensagem {
    Texto(String),
    Imagem { url: String, largura: u32, altura: u32 },
    Ping,
}

fn processar(msg: &Mensagem) {
    match msg {
        Mensagem::Texto(t) => println!("Texto: {}", t),
        Mensagem::Imagem { url, largura, altura } => {
            println!("Imagem: {} ({}x{})", url, largura, altura)
        }
        Mensagem::Ping => println!("Ping!"),
    }
}
```

**Zig — Tagged unions:**

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

const Mensagem = union(enum) {
    texto: []const u8,
    imagem: struct {
        url: []const u8,
        largura: u32,
        altura: u32,
    },
    ping: void,
};

fn processar(msg: Mensagem) void {
    switch (msg) {
        .texto => |t| std.debug.print("Texto: {s}\n", .{t}),
        .imagem => |img| std.debug.print(
            "Imagem: {s} ({d}x{d})\n",
            .{ img.url, img.largura, img.altura },
        ),
        .ping => std.debug.print("Ping!\n", .{}),
    }
}

pub fn main() void {
    const msg1 = Mensagem{ .texto = "Olá!" };
    const msg2 = Mensagem{ .imagem = .{
        .url = "foto.png",
        .largura = 800,
        .altura = 600,
    } };
    const msg3 = Mensagem{ .ping = {} };

    processar(msg1);
    processar(msg2);
    processar(msg3);
}
```

### Testes

**Rust:**

```rust
fn soma(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_soma() {
        assert_eq!(soma(2, 3), 5);
    }

    #[test]
    #[should_panic]
    fn test_overflow() {
        let _ = soma(i32::MAX, 1);
    }
}
```

**Zig:**

```zig
const std = @import("std");
const expect = std.testing.expect;

fn soma(a: i32, b: i32) i32 {
    return a + b;
}

test "soma basica" {
    try expect(soma(2, 3) == 5);
}

test "soma com valores negativos" {
    try expect(soma(-1, 1) == 0);
    try expect(soma(-5, -3) == -8);
}

test "soma com zero" {
    try expect(soma(0, 42) == 42);
}
```

Em Zig, blocos `test` ficam no mesmo arquivo que o código, sem necessidade de módulos de teste separados. Execute com `zig build test` ou `zig test src/arquivo.zig`.

## Quando Escolher Zig vs Rust

Nem toda ferramenta é ideal para todo trabalho. Aqui está uma comparação honesta de quando cada linguagem brilha:

### Escolha Rust Quando:

- **Segurança de memória em tempo de compilação é crítica**: Sistemas financeiros, infraestrutura de segurança.
- **O ecossistema crates.io importa**: Rust tem um ecossistema massivo de bibliotecas maduras.
- **Async complexo é necessário**: Tokio é extremamente maduro para servidores de alta concorrência.
- **A equipe valoriza garantias do compilador**: O borrow checker previne classes inteiras de bugs antes do deploy.
- **WebAssembly no frontend**: Rust tem o melhor suporte WASM com wasm-bindgen e Yew.

### Escolha Zig Quando:

- **Interop com C é prioridade**: Zig importa headers C nativamente, perfeito para usar bibliotecas C existentes.
- **Binários mínimos são necessários**: Sem runtime, Zig gera executáveis extremamente pequenos. Ideal para sistemas embarcados.
- **Cross-compilation é frequente**: Zig compila para qualquer plataforma com um único comando, sem configurar toolchains extras.
- **Simplicidade importa mais que garantias**: Para equipes que preferem explicitação a abstrações complexas.
- **Substituir C em projetos legados**: Zig pode ser adotado incrementalmente em projetos C existentes.
- **Kernels e drivers**: Zig não tem runtime, não tem exceções, não tem hidden control flow.

### Tabela de Decisão por Domínio

| Domínio | Rust | Zig | Notas |
|---|---|---|---|
| **Web backend** | Excelente | Bom | Rust tem Actix/Axum maduros |
| **Sistemas embarcados** | Bom | Excelente | Zig tem binários menores |
| **Game engines** | Bom | Excelente | Zig tem controle total, sem GC |
| **CLI tools** | Excelente | Bom | Rust tem clap, serde, ecossistema |
| **Kernels/drivers** | Bom | Excelente | Linux adota Zig oficialmente |
| **Networking** | Excelente | Bom | Tokio é muito maduro |
| **Interop com C** | Bom | Excelente | Zig importa headers direto |
| **WASM** | Excelente | Bom | Rust tem melhor tooling WASM |
| **DevOps/Infra** | Excelente | Bom | Rust tem ecossistema maior |

### Podem Coexistir?

Sim. Zig e Rust podem ser usados juntos no mesmo projeto. Ambos geram código compatível com a ABI C, então é possível:

- Escrever uma biblioteca em Zig e usá-la em Rust via FFI.
- Usar o compilador C do Zig (`zig cc`) para compilar dependências C de projetos Rust.
- Substituir gradualmente componentes C em um projeto misto Rust/C usando Zig.

```zig
// lib_zig.zig — exporta função com ABI C
export fn calcular_hash(dados: [*]const u8, tamanho: usize) u64 {
    var hash: u64 = 5381;
    for (dados[0..tamanho]) |byte| {
        hash = ((hash << 5) +% hash) +% byte;
    }
    return hash;
}
```

```rust
// main.rs — importa a função Zig
extern "C" {
    fn calcular_hash(dados: *const u8, tamanho: usize) -> u64;
}

fn main() {
    let dados = b"Zig + Rust";
    let hash = unsafe { calcular_hash(dados.as_ptr(), dados.len()) };
    println!("Hash: {}", hash);
}
```

## Próximos Passos

Agora que você entende como os conceitos de Rust se traduzem para Zig, aqui estão os próximos recursos recomendados:

1. **[Como Instalar Zig](/tutoriais/como-instalar-zig/)** — Configure seu ambiente de desenvolvimento Zig.
2. **[Zig Build System](/tutoriais/zig-build-system/)** — Domine o `build.zig` e substitua o Cargo na sua rotina.
3. **[Zig para Desenvolvedores](/tutoriais/zig-para-desenvolvedores/)** — Guia geral com exemplos práticos para programadores experientes.
4. **[Zig Async e io_uring](/tutoriais/zig-async-iouring/)** — Aprofunde-se no modelo async de Zig sem runtime.
5. **[FFI e Interoperabilidade com C](/tutoriais/zig-c-interoperabilidade/)** — Como integrar bibliotecas C sem bindings manuais.
6. **[Comptime e Reflexão](/tutoriais/zig-comptime-reflection/)** — Domine o `comptime` como substituto de macros.
7. **[Tratamento de Erros em Zig](/tutoriais/tratamento-de-erros-em-zig/)** — Guia completo sobre error unions e padrões de erro.
8. **[Testes em Zig](/tutoriais/testes-zig/)** — Como testar código Zig de forma eficiente.

### Recursos Externos

- [Documentação oficial do Zig](https://ziglang.org/documentation/master/) — Referência completa da linguagem.
- [Zig Learn](https://ziglearn.org/) — Tutorial interativo em inglês.
- [Zig no GitHub](https://github.com/ziglang/zig) — Código-fonte e issues.

Se você vem de Rust, vai encontrar em Zig uma linguagem que respeita sua experiência com sistemas, mas oferece um caminho diferente: menos abstração, mais controle explícito, e uma simplicidade que pode ser surpreendentemente produtiva.
