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
| Kotlin | Zig | Notas |
|---|---|---|
val x = 42 | const x: i32 = 42 | Imutável |
var x = 42 | var x: i32 = 42 | Mutável |
String? (nullable) | ?[]const u8 (optional) | Null safety |
data class | struct | Sem métodos gerados |
sealed class | union(enum) | Tagged unions |
interface | Comptime duck typing | Sem dispatch dinâmico |
Generics <T> | comptime T: type | Compilação |
when | switch | Exaustivo em ambas |
?. (safe call) | if (opt) |v| | Unwrap seguro |
?: (Elvis) | orelse | Valor padrão |
| Coroutines | std.Thread | Sem green threads |
| GC (JVM) | Allocators | Manual |
fun | fn | Funções |
| Extension functions | Não existe | Usar 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.