Zig para Programadores Kotlin

Introdução

Kotlin e Zig são linguagens com prioridades diferentes mas que compartilham valores interessantes. Ambas valorizam null safety, expressividade concisa e pragmatismo. Kotlin opera no ecossistema JVM (e Kotlin/Native), enquanto Zig é uma linguagem de sistemas que compila diretamente para código nativo sem runtime.

Este guia ajuda desenvolvedores Kotlin a entender Zig mapeando conceitos familiares. Para perspectivas de outras linguagens, veja Zig para Programadores Java e Zig para Programadores C#.

Mapeamento de Conceitos

KotlinZigNotas
val x = 42const x: i32 = 42Imutável
var x = 42var x: i32 = 42Mutável
String? (nullable)?[]const u8 (optional)Null safety
data classstructSem métodos gerados
sealed classunion(enum)Tagged unions
interfaceComptime duck typingSem dispatch dinâmico
Generics <T>comptime T: typeCompilação
whenswitchExaustivo em ambas
?. (safe call)if (opt) |v|Unwrap seguro
?: (Elvis)orelseValor padrão
Coroutinesstd.ThreadSem green threads
GC (JVM)AllocatorsManual
funfnFunções
Extension functionsNão existeUsar funções livres

Null Safety

Kotlin e Zig tratam null safety como prioridade, mas com abordagens diferentes:

Kotlin

val nome: String? = obterNome()
val tamanho = nome?.length ?: 0
val garantido = nome ?: "padrão"

// Smart cast
if (nome != null) {
    println(nome.length)  // nome é String aqui, não String?
}

Zig

const nome: ?[]const u8 = obterNome();
const tamanho = if (nome) |n| n.len else 0;
const garantido = nome orelse "padrão";

// Unwrap seguro
if (nome) |n| {
    std.debug.print("Tamanho: {}\n", .{n.len});
}

Ambas as linguagens forçam tratamento explícito de valores nulos em tempo de compilação. A diferença é sintática — Kotlin usa ?. e ?:, Zig usa if (opt) |v| e orelse.

Data Classes vs Structs

Kotlin

data class Ponto(val x: Double, val y: Double)

val p1 = Ponto(1.0, 2.0)
val p2 = p1.copy(x = 3.0)
println(p1 == p2)  // comparação estrutural automática
println(p1)  // toString automático: Ponto(x=1.0, y=2.0)

Zig

const Ponto = struct {
    x: f64,
    y: f64,

    pub fn copiar(self: Ponto) Ponto {
        return self; // cópia por valor
    }

    pub fn comX(self: Ponto, novo_x: f64) Ponto {
        var copia = self;
        copia.x = novo_x;
        return copia;
    }

    pub fn igual(self: Ponto, outro: Ponto) bool {
        return self.x == outro.x and self.y == outro.y;
    }
};

const p1 = Ponto{ .x = 1.0, .y = 2.0 };
const p2 = p1.comX(3.0);
const iguais = p1.igual(p2);

Zig structs são value types por padrão (como data class), mas não geram automaticamente equals, hashCode, toString ou copy. Você implementa o que precisa.

Sealed Classes vs Tagged Unions

Kotlin

sealed class Resultado {
    data class Sucesso(val valor: String) : Resultado()
    data class Erro(val codigo: Int, val mensagem: String) : Resultado()
    object Carregando : Resultado()
}

fun processar(r: Resultado) = when (r) {
    is Resultado.Sucesso -> println("OK: ${r.valor}")
    is Resultado.Erro -> println("Erro ${r.codigo}: ${r.mensagem}")
    Resultado.Carregando -> println("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 => |valor| std.debug.print("OK: {s}\n", .{valor}),
        .erro => |e| std.debug.print("Erro {}: {s}\n", .{ e.codigo, e.mensagem }),
        .carregando => std.debug.print("Carregando...\n", .{}),
    }
}

union(enum) em Zig é funcionalmente equivalente a sealed class em Kotlin. Ambas garantem exaustividade no switch/when.

Error Handling

Kotlin

// Kotlin pode usar exceções...
try {
    val resultado = operacaoPerigosa()
} catch (e: IOException) {
    println("Erro: ${e.message}")
}

// ...ou Result type
fun dividir(a: Double, b: Double): Result<Double> =
    if (b == 0.0) Result.failure(ArithmeticException("Divisão por zero"))
    else Result.success(a / b)

dividir(10.0, 3.0)
    .onSuccess { println("Resultado: $it") }
    .onFailure { println("Erro: ${it.message}") }

Zig

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

// Usar try para propagar
const resultado = try dividir(10, 3);

// Ou catch para tratar
const resultado2 = dividir(10, 3) catch |err| {
    std.debug.print("Erro: {}\n", .{err});
    return;
};

Zig não tem exceções. Todo erro é tratado via error unions. Veja Error Sets Customizados e Padrões Try/Catch.

Generics vs Comptime

Kotlin

class Fila<T> {
    private val items = mutableListOf<T>()
    fun enfileirar(item: T) = items.add(item)
    fun desenfileirar(): T? = items.removeFirstOrNull()
}

Zig

fn Fila(comptime T: type) type {
    return struct {
        items: std.ArrayList(T),

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{ .items = std.ArrayList(T).init(allocator) };
        }

        pub fn deinit(self: *Self) void {
            self.items.deinit();
        }

        pub fn enfileirar(self: *Self, item: T) !void {
            try self.items.append(item);
        }

        pub fn desenfileirar(self: *Self) ?T {
            if (self.items.items.len == 0) return null;
            return self.items.orderedRemove(0);
        }
    };
}

Generics em Kotlin existem em runtime (com type erasure na JVM). Em Zig, comptime gera código especializado para cada tipo.

Coroutines vs Threads

Kotlin

suspend fun buscarDados(): String {
    delay(1000) // suspende sem bloquear thread
    return "dados"
}

fun main() = runBlocking {
    val resultado = async { buscarDados() }
    println(resultado.await())
}

Zig

fn buscarDados() ![]const u8 {
    // operação síncrona — Zig não tem coroutines
    std.time.sleep(1_000_000_000); // 1 segundo
    return "dados";
}

pub fn main() !void {
    const thread = try std.Thread.spawn(.{}, struct {
        fn run() void {
            const dados = buscarDados() catch return;
            std.debug.print("{s}\n", .{dados});
        }
    }.run, .{});
    thread.join();
}

Zig usa threads do SO, sem green threads ou coroutines. Veja Concorrência em Zig.

Extension Functions

Kotlin tem extension functions. Zig não:

Kotlin

fun String.repetirComSeparador(n: Int, sep: String): String =
    (1..n).joinToString(sep) { this }

println("Zig".repetirComSeparador(3, "-"))  // Zig-Zig-Zig

Zig

// Em Zig, usar funções livres
fn repetirComSeparador(
    allocator: std.mem.Allocator,
    texto: []const u8,
    n: usize,
    sep: []const u8,
) ![]u8 {
    var resultado = std.ArrayList(u8).init(allocator);
    for (0..n) |i| {
        if (i > 0) try resultado.appendSlice(sep);
        try resultado.appendSlice(texto);
    }
    return resultado.toOwnedSlice();
}

Gerenciamento de Memória

A diferença fundamental: Kotlin roda na JVM com garbage collector. Zig usa allocators manuais:

// Padrão de gerenciamento de memória em Zig
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var lista = std.ArrayList([]const u8).init(allocator);
    defer lista.deinit();

    try lista.append("Olá");
    try lista.append("Mundo");
}

Veja ArenaAllocator e Substituir malloc/free por Allocators.

Conclusão

Programadores Kotlin encontrarão paralelos claros em Zig: null safety via optionals, sealed classes via tagged unions, e when via switch exaustivo. As maiores diferenças são a ausência de GC, a falta de coroutines, e a necessidade de gerenciamento manual de memória.

A transição exige disciplina com alocações, mas oferece performance de baixo nível e controle total. Para começar, visite Introdução ao Zig e Como Instalar Zig.

Continue aprendendo Zig

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