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
| Swift | Zig | Notas |
|---|---|---|
let x = 42 | const x: i32 = 42 | Imutável por padrão |
var x = 42 | var x: i32 = 42 | Mutável |
Optional<T> / T? | ?T | Conceito idêntico |
protocol | Comptime duck typing | Sem dispatch dinâmico |
enum | enum | Tagged unions similares |
struct (value type) | struct | Similar |
class (ref type) | Ponteiro para struct | Manual |
| ARC | Allocators | Diferença fundamental |
guard let | orelse return | Similar |
if let | if (opt) |val| | Unwrap seguro |
defer | defer | Idêntico em conceito |
throw/catch | Error unions | Sem exceções |
| Generics | comptime | Compilação |
String | []const u8 | Bytes 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.