Zig para Programadores Swift

Introdução

Se você é desenvolvedor Swift, muitos conceitos de Zig serão familiares. Ambas as linguagens valorizam segurança, optionals explícitos, enums poderosos e tratamento de erros sem exceções tradicionais. A diferença fundamental é que Swift usa ARC (Automatic Reference Counting) e Zig usa alocadores explícitos — o programador gerencia memória manualmente.

Este guia mapeia conceitos de Swift para Zig, facilitando a transição. Para perspectivas de outras linguagens, veja Zig para Programadores Kotlin e Zig para Programadores C#.

Mapeamento de Conceitos

SwiftZigNotas
let x = 42const x: i32 = 42Imutável por padrão
var x = 42var x: i32 = 42Mutável
Optional<T> / T??TConceito idêntico
protocolComptime duck typingSem dispatch dinâmico
enumenumTagged unions similares
struct (value type)structSimilar
class (ref type)Ponteiro para structManual
ARCAllocatorsDiferença fundamental
guard letorelse returnSimilar
if letif (opt) |val|Unwrap seguro
deferdeferIdêntico em conceito
throw/catchError unionsSem exceções
GenericscomptimeCompilação
String[]const u8Bytes UTF-8

Optionals: Surpreendentemente Similar

Optionals em Swift e Zig são quase idênticos em conceito:

Swift

let valor: Int? = 42
if let v = valor {
    print("Valor: \(v)")
}
let seguro = valor ?? 0
guard let v = valor else { return }

Zig

const valor: ?i32 = 42;
if (valor) |v| {
    std.debug.print("Valor: {}\n", .{v});
}
const seguro = valor orelse 0;
const v = valor orelse return;

A sintaxe difere, mas a semântica é praticamente a mesma. Ambas as linguagens forçam tratamento explícito de valores opcionais.

Enums e Tagged Unions

Swift tem enums com associated values. Zig tem tagged unions — conceitualmente o mesmo:

Swift

enum Resultado {
    case sucesso(String)
    case erro(Int, String)
    case carregando
}

func processar(_ r: Resultado) {
    switch r {
    case .sucesso(let msg):
        print("OK: \(msg)")
    case .erro(let codigo, let msg):
        print("Erro \(codigo): \(msg)")
    case .carregando:
        print("Carregando...")
    }
}

Zig

const Resultado = union(enum) {
    sucesso: []const u8,
    erro: struct { codigo: i32, mensagem: []const u8 },
    carregando: void,
};

fn processar(r: Resultado) void {
    switch (r) {
        .sucesso => |msg| std.debug.print("OK: {s}\n", .{msg}),
        .erro => |e| std.debug.print("Erro {}: {s}\n", .{ e.codigo, e.mensagem }),
        .carregando => std.debug.print("Carregando...\n", .{}),
    }
}

Protocolos vs Comptime

Swift

protocol Serializavel {
    func serializar() -> Data
    static func deserializar(de data: Data) -> Self
}

struct Usuario: Serializavel {
    let nome: String
    func serializar() -> Data { /* ... */ }
    static func deserializar(de data: Data) -> Usuario { /* ... */ }
}

func salvar<T: Serializavel>(_ item: T) {
    let dados = item.serializar()
    // salvar dados...
}

Zig

fn salvar(item: anytype) !void {
    const dados = item.serializar();
    // salvar dados...
    _ = dados;
}

// Ou com verificação explícita
fn salvarVerificado(comptime T: type, item: T) !void {
    comptime {
        if (!@hasDecl(T, "serializar")) {
            @compileError("Tipo deve ter método serializar()");
        }
    }
    const dados = item.serializar();
    _ = dados;
}

Zig usa “duck typing” em tempo de compilação em vez de protocolos formais. Se o tipo tem o método necessário, funciona. Caso contrário, o compilador gera um erro claro.

Error Handling

Swift e Zig têm abordagens similares de error handling — ambas evitam exceções tradicionais:

Swift

enum ArquivoError: Error {
    case naoEncontrado
    case semPermissao
    case corrompido
}

func lerArquivo(_ caminho: String) throws -> String {
    guard FileManager.default.fileExists(atPath: caminho) else {
        throw ArquivoError.naoEncontrado
    }
    return try String(contentsOfFile: caminho)
}

do {
    let conteudo = try lerArquivo("config.txt")
    print(conteudo)
} catch ArquivoError.naoEncontrado {
    print("Arquivo não encontrado")
}

Zig

const ArquivoError = error{
    NaoEncontrado,
    SemPermissao,
    Corrompido,
};

fn lerArquivo(allocator: std.mem.Allocator, caminho: []const u8) ![]u8 {
    const arquivo = std.fs.cwd().openFile(caminho, .{}) catch {
        return error.NaoEncontrado;
    };
    defer arquivo.close();
    return arquivo.readToEndAlloc(allocator, 1024 * 1024);
}

const conteudo = lerArquivo(allocator, "config.txt") catch |err| switch (err) {
    error.NaoEncontrado => {
        std.debug.print("Arquivo não encontrado\n", .{});
        return;
    },
    else => return err,
};
defer allocator.free(conteudo);

Veja Error Sets Customizados e Padrões Errdefer.

ARC vs Allocators

Swift

class MinhaClasse {
    var dados: [Int]
    init() { dados = [] }
    // ARC libera automaticamente quando refcount = 0
}

var obj = MinhaClasse()
var ref = obj  // refcount = 2
ref = MinhaClasse()  // refcount de obj volta para 1

Zig

const MinhaStruct = struct {
    dados: std.ArrayList(i32),

    pub fn init(allocator: std.mem.Allocator) MinhaStruct {
        return .{ .dados = std.ArrayList(i32).init(allocator) };
    }

    pub fn deinit(self: *MinhaStruct) void {
        self.dados.deinit();
    }
};

var obj = MinhaStruct.init(allocator);
defer obj.deinit(); // VOCÊ é responsável pela liberação

Em Zig, não há contagem de referências automática. O padrão init/defer deinit garante que recursos sejam liberados. Veja GeneralPurposeAllocator e Detectar Vazamentos de Memória.

Closures e Funções

Swift

let numeros = [1, 2, 3, 4, 5]
let dobrados = numeros.map { $0 * 2 }
let filtrados = numeros.filter { $0 > 3 }

Zig

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

// Zig não tem map/filter built-in — usar loops explícitos
var resultado = std.ArrayList(i32).init(allocator);
defer resultado.deinit();

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

Zig não tem closures com captura de variáveis como Swift. Funções anônimas existem mas não capturam escopo. Isso é intencional — mantém a linguagem previsível em termos de alocações.

Strings

Swift

let nome: String = "Zig Brasil"
let tamanho = nome.count
let maiusculo = nome.uppercased()
let contem = nome.contains("Brasil")

Zig

const nome: []const u8 = "Zig Brasil";
const tamanho = nome.len; // bytes, não caracteres Unicode
const contem = std.mem.indexOf(u8, nome, "Brasil") != null;

// Para uppercase, iterar manualmente
var buffer: [nome.len]u8 = undefined;
for (nome, 0..) |c, i| {
    buffer[i] = std.ascii.toUpper(c);
}

Strings em Zig são slices de bytes ([]const u8), não tipos ricos como em Swift. Para manipulações avançadas, veja Manipulação de Strings e Converter Maiúsculas e Minúsculas.

Testes

Swift

func testSoma() {
    XCTAssertEqual(soma(2, 3), 5)
}

Zig

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

Testes em Zig são inline e executados com zig test. Veja Testes Unitários e Testes com Allocator.

Conclusão

Programadores Swift têm uma vantagem na transição para Zig: os conceitos de optionals, enums com dados associados, e error handling sem exceções são muito similares. A maior adaptação será o gerenciamento manual de memória — trocar ARC por allocators explícitos requer disciplina mas oferece controle total.

Para começar, visite Introdução ao Zig e Como Instalar Zig. Para entender quando Zig é adequado, leia Quando Usar Zig.

Continue aprendendo Zig

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