Introdução
Se você é desenvolvedor C#, Zig vai representar uma mudança significativa de paradigma. C# é uma linguagem de alto nível com garbage collector, classes, herança, LINQ e um ecossistema rico (.NET). Zig é uma linguagem de sistemas sem GC, sem classes, sem exceções e com controle manual de memória.
Isso não significa que Zig é “pior” — apenas que opera em outro nível. Este guia ajuda você a traduzir conceitos familiares de C# para Zig. Para outras perspectivas, veja Zig para Programadores Kotlin e Zig para Programadores Java.
Mapeamento de Conceitos
| C# | Zig | Notas |
|---|---|---|
class | struct | Zig não tem classes nem herança |
interface | Comptime duck typing | Sem vtable |
var x = 42 | const x = 42 | Inferência de tipo |
List<T> | std.ArrayList(T) | Requer allocator |
Dictionary<K,V> | std.HashMap(K,V,...) | Requer allocator |
try/catch | try/catch (error unions) | Sem exceções |
using | defer | Limpeza de recursos |
async/await | Sem equivalente direto | Usar threads |
null | null (optional) | Tipo ?T |
Nullable<T> | ?T | Optionals nativos |
| Generics | comptime | Resolvido em compilação |
| GC | Allocators explícitos | Diferença fundamental |
string | []const u8 | Bytes, não chars |
| LINQ | Loops explícitos | Sem query syntax |
Classes vs Structs
C# usa classes com herança. Zig usa structs sem herança:
C#
public class Animal {
public string Nome { get; set; }
public virtual void Falar() => Console.WriteLine("...");
}
public class Cachorro : Animal {
public override void Falar() => Console.WriteLine("Au au!");
}
Zig
const Animal = struct {
nome: []const u8,
falarFn: *const fn (*const Animal) void,
pub fn falar(self: *const Animal) void {
self.falarFn(self);
}
};
fn falarCachorro(_: *const Animal) void {
std.debug.print("Au au!\n", .{});
}
const cachorro = Animal{
.nome = "Rex",
.falarFn = falarCachorro,
};
Em vez de herança, Zig usa composição e ponteiros de função quando polimorfismo de runtime é necessário. Para polimorfismo de compilação, use comptime:
fn alimentar(animal: anytype) void {
animal.comer();
}
Generics vs Comptime
C#
public class Pilha<T> {
private List<T> _items = new();
public void Push(T item) => _items.Add(item);
public T Pop() => _items[^1]; // simplificado
}
var pilha = new Pilha<int>();
pilha.Push(42);
Zig
fn Pilha(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 push(self: *Self, item: T) !void {
try self.items.append(item);
}
pub fn pop(self: *Self) ?T {
return self.items.popOrNull();
}
};
}
var pilha = Pilha(u32).init(allocator);
defer pilha.deinit();
try pilha.push(42);
A diferença fundamental: generics em C# existem em runtime (com type erasure parcial para value types); em Zig, comptime gera código especializado para cada tipo em tempo de compilação.
Error Handling
C#
try {
var conteudo = File.ReadAllText("config.json");
var config = JsonSerializer.Deserialize<Config>(conteudo);
}
catch (FileNotFoundException ex) {
Console.WriteLine($"Arquivo não encontrado: {ex.FileName}");
}
catch (JsonException ex) {
Console.WriteLine($"JSON inválido: {ex.Message}");
}
Zig
const config = lerConfig("config.json") catch |err| switch (err) {
error.FileNotFound => {
std.debug.print("Arquivo não encontrado\n", .{});
return;
},
error.InvalidJson => {
std.debug.print("JSON inválido\n", .{});
return;
},
else => return err,
};
Zig não tem exceções. Erros são valores de retorno tipados. O compilador garante que todo erro seja tratado ou propagado com try. Veja Error Sets Customizados e Error Logging.
Nullable e Optional
C#
int? valor = null;
if (valor.HasValue) {
Console.WriteLine(valor.Value);
}
// Ou com pattern matching
if (valor is int v) {
Console.WriteLine(v);
}
Zig
const valor: ?i32 = null;
if (valor) |v| {
std.debug.print("{}\n", .{v});
}
// Ou com orelse
const seguro = valor orelse 0;
Gerenciamento de Memória
Esta é a maior mudança para programadores C#. Em C#, o GC gerencia tudo. Em Zig, você é responsável:
C#
var lista = new List<string>();
lista.Add("Olá");
lista.Add("Mundo");
// GC libera quando não há mais referências
Zig
var lista = std.ArrayList([]const u8).init(allocator);
defer lista.deinit(); // VOCÊ libera
try lista.append("Olá");
try lista.append("Mundo");
O padrão em Zig é init + defer deinit — similar ao using em C# mas mais explícito. Veja ArenaAllocator e GeneralPurposeAllocator.
LINQ vs Loops Explícitos
C# tem LINQ para consultas declarativas. Zig usa loops explícitos:
C#
var resultado = numeros
.Where(n => n > 10)
.Select(n => n * 2)
.OrderBy(n => n)
.ToList();
Zig
var resultado = std.ArrayList(i32).init(allocator);
defer resultado.deinit();
for (numeros) |n| {
if (n > 10) {
try resultado.append(n * 2);
}
}
std.mem.sort(i32, resultado.items, {}, std.sort.asc(i32));
Zig é mais verboso aqui, mas o código é mais previsível em termos de alocações e performance.
using vs defer
C#
using var stream = File.OpenRead("dados.bin");
using var reader = new BinaryReader(stream);
var dados = reader.ReadBytes(1024);
Zig
const arquivo = try std.fs.cwd().openFile("dados.bin", .{});
defer arquivo.close();
var buffer: [1024]u8 = undefined;
const lidos = try arquivo.readAll(&buffer);
defer em Zig funciona no escopo do bloco e executa quando o bloco termina, independentemente de erro ou retorno normal. errdefer executa apenas em caso de erro — sem equivalente em C#. Veja Padrões Errdefer.
Testes
C#
[Test]
public void TestSoma() {
Assert.AreEqual(5, Calculadora.Soma(2, 3));
}
Zig
test "soma" {
try std.testing.expectEqual(@as(i32, 5), soma(2, 3));
}
Testes em Zig são inline no código fonte e executados com zig test. Veja Testes Unitários Básicos e Testes com Allocator.
Conclusão
A transição de C# para Zig é significativa — você troca a conveniência de uma linguagem gerenciada pelo controle total de uma linguagem de sistemas. As maiores adaptações serão o gerenciamento manual de memória, a ausência de classes/herança, e a troca de exceções por error unions.
O benefício é código sem overhead de runtime, binários pequenos, performance previsível, e a capacidade de trabalhar em qualquer nível — de drivers de hardware a aplicações de servidor.
Para começar, visite Introdução ao Zig e Como Instalar Zig. Se quiser entender quando Zig é a ferramenta certa, leia Quando Usar Zig.