Guia de Migração: Rust para Zig

Introdução

Migrar de Rust para Zig pode parecer contra-intuitivo — por que trocar as garantias de segurança do borrow checker? A resposta geralmente envolve simplicidade: Zig resolve os mesmos problemas com menos complexidade, sem lifetimes, sem traits, sem borrow checker. A segurança vem de verificações em runtime (em debug) e de boas práticas com allocators.

Para comparação detalhada, veja Zig vs Rust e Zig para Desenvolvedores Rust.

Pré-requisitos

Ownership e Borrowing

Rust

fn processar(dados: Vec<u8>) -> Vec<u8> {
    // dados é movido para esta função
    dados.into_iter().map(|b| b + 1).collect()
}

fn analisar(dados: &[u8]) -> usize {
    // dados é emprestado (borrowed)
    dados.iter().filter(|&&b| b > 0).count()
}

Zig

fn processar(allocator: std.mem.Allocator, dados: []const u8) ![]u8 {
    const resultado = try allocator.alloc(u8, dados.len);
    for (dados, 0..) |b, i| {
        resultado[i] = b + 1;
    }
    return resultado;
}

fn analisar(dados: []const u8) usize {
    var count: usize = 0;
    for (dados) |b| {
        if (b > 0) count += 1;
    }
    return count;
}

Em Zig, não há ownership tracking automático. A responsabilidade é do programador:

  • Quem aloca, documenta quem libera
  • Use defer e errdefer para garantir limpeza
  • Allocators tornam alocações visíveis

Veja Padrões Errdefer e ArenaAllocator.

Result e Option

Rust

fn dividir(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Divisão por zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn buscar(id: u32) -> Option<Usuario> {
    if id == 0 { None } else { Some(Usuario { id }) }
}

Zig

fn dividir(a: f64, b: f64) !f64 {
    if (b == 0) return error.DivisaoPorZero;
    return a / b;
}

fn buscar(id: u32) ?Usuario {
    if (id == 0) return null;
    return Usuario{ .id = id };
}

Result<T,E> em Rust mapeia para !T (error union) em Zig. Option<T> mapeia para ?T (optional). Veja Error Sets Customizados.

Encadeamento

// Rust
let resultado = valor
    .ok_or("não encontrado")?
    .processar()?
    .finalizar();
// Zig
const intermediario = valor orelse return error.NaoEncontrado;
const processado = try intermediario.processar();
const resultado = processado.finalizar();

Traits para Comptime

Rust

trait Serializar {
    fn serializar(&self) -> Vec<u8>;
    fn tamanho(&self) -> usize;
}

fn salvar<T: Serializar>(item: &T) -> Result<(), std::io::Error> {
    let dados = item.serializar();
    std::fs::write("saida.bin", &dados)
}

Zig

fn salvar(item: anytype) !void {
    const dados = item.serializar();
    defer item.allocator.free(dados); // se aplicável
    try std.fs.cwd().writeFile("saida.bin", dados);
}

// Com verificação explícita em compilação
fn salvarVerificado(comptime T: type, item: T) !void {
    comptime {
        if (!@hasDecl(T, "serializar")) {
            @compileError("Tipo deve implementar serializar()");
        }
    }
    const dados = item.serializar();
    try std.fs.cwd().writeFile("saida.bin", dados);
}

Pattern Matching

Rust

match resultado {
    Ok(valor) => println!("Sucesso: {}", valor),
    Err(e) => eprintln!("Erro: {}", e),
}

match forma {
    Forma::Circulo { raio } => PI * raio * raio,
    Forma::Retangulo { largura, altura } => largura * altura,
    Forma::Triangulo { base, altura } => base * altura / 2.0,
}

Zig

if (resultado) |valor| {
    std.debug.print("Sucesso: {}\n", .{valor});
} else |err| {
    std.debug.print("Erro: {}\n", .{err});
}

switch (forma) {
    .circulo => |c| std.math.pi * c.raio * c.raio,
    .retangulo => |r| r.largura * r.altura,
    .triangulo => |t| t.base * t.altura / 2.0,
}

Enums com Dados

Rust

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

Zig

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

Iteradores

Rust

let soma: i32 = numeros
    .iter()
    .filter(|&&n| n > 0)
    .map(|&n| n * 2)
    .sum();

Zig

var soma: i32 = 0;
for (numeros) |n| {
    if (n > 0) {
        soma += n * 2;
    }
}

Zig não tem iteradores lazy como Rust. Use loops for explícitos — mais verboso, mas mais claro sobre o que acontece.

Lifetimes

Zig não tem lifetimes. A gestão de tempo de vida é responsabilidade do programador:

Rust

struct Parser<'a> {
    input: &'a str,
    pos: usize,
}

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

Zig

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

    pub fn init(input: []const u8) Parser {
        return .{ .input = input, .pos = 0 };
    }
};
// O programador garante que input vive mais que Parser

Vec, HashMap, String

RustZig
Vec<T>std.ArrayList(T)
HashMap<K,V>std.HashMap(K,V,...)
Stringstd.ArrayList(u8)
&str[]const u8
Box<T>allocator.create(T)
Arc<T>Manual (sem equivalente direto)

Closures

Rust

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

Zig

// Zig não tem closures com captura
// Usar funções nomeadas ou structs
fn dobrar(x: i32) i32 {
    return x * 2;
}

// Ou usar ponteiros de função
const operacao: *const fn (i32) i32 = dobrar;

Testes

Rust

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

Zig

test "soma" {
    try std.testing.expectEqual(@as(i32, 5), soma(2, 3));
}

Veja Testes Unitários e Testes com Allocator.

Cargo para build.zig

Veja Migrar de Makefile para build.zig para conceitos similares. O build.zig substitui tanto Cargo.toml quanto build.rs.

Conclusão

A migração de Rust para Zig troca verificações de compilação (borrow checker) por simplicidade e controle explícito. O código resultante é mais simples de ler e entender, mas exige mais disciplina do programador para garantir segurança de memória.

Se a complexidade do borrow checker está diminuindo a produtividade da sua equipe e o projeto não exige garantias formais de segurança de memória, Zig pode ser uma alternativa pragmática. Consulte Quando Usar Zig para avaliar se é a escolha certa.

Continue aprendendo Zig

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