Se você é um programador Python e está curioso sobre programação de sistemas, a zig lang é uma excelente porta de entrada. A linguagem Zig oferece performance próxima ao C, controle total sobre a memória e uma sintaxe surpreendentemente legível para quem vem de linguagens de alto nível. Neste guia, vamos explorar as principais diferenças entre Python e Zig, com exemplos lado a lado para facilitar sua transição.
Por Que um Programador Python Deveria Aprender Zig
Python é fantástico para prototipagem rápida, ciência de dados, automação e desenvolvimento web. No entanto, existem situações em que Python simplesmente não entrega a performance necessária. É aí que Zig entra.
Aqui estão os principais motivos para considerar Zig:
- Performance real: Zig compila para código nativo otimizado, atingindo velocidades comparáveis ao C. Operações que levam segundos em Python podem levar microssegundos em Zig.
- Entendimento profundo: Aprender Zig vai transformar a forma como você pensa sobre memória, alocação e eficiência, tornando você um programador Python melhor também.
- Extensões nativas: Você pode escrever módulos nativos em Zig para acelerar partes críticas do seu código Python.
- Sem garbage collector: Zig dá controle total sobre a memória, eliminando pausas imprevisíveis do GC.
- Cross-compilation embutida: Compile para qualquer plataforma diretamente, sem configuração complexa.
Diferenças Fundamentais: Python vs Zig
Antes de mergulhar nos exemplos, é importante entender as diferenças filosóficas entre as duas linguagens.
| Aspecto | Python | Zig |
|---|---|---|
| Tipagem | Dinâmica | Estática |
| Execução | Interpretado (CPython) | Compilado (LLVM) |
| Memória | Garbage Collector | Gerenciamento manual |
| Null/None | None é um valor | null é tipo opcional explícito |
| Erros | Exceções (try/except) | Error unions |
| Paradigma | Multiparadigma (OOP forte) | Procedural com generics |
| Velocidade | Lenta (relativo) | Muito rápida |
Variáveis e Tipos
Em Python, variáveis não têm tipo declarado. Em Zig, cada variável precisa de um tipo explícito ou inferido.
Python:
nome = "Maria"
idade = 28
pi = 3.14159
ativo = True
Zig:
const nome = "Maria";
var idade: u32 = 28;
const pi: f64 = 3.14159;
const ativo: bool = true;
Observe que Zig distingue entre const (imutável) e var (mutável). Isso é parecido com boas práticas em Python onde evitamos reatribuir variáveis, mas em Zig o compilador realmente impõe a imutabilidade.
Os tipos numéricos em Zig são explícitos sobre tamanho e sinalização: u32 é um inteiro sem sinal de 32 bits, i64 é com sinal de 64 bits, f64 é ponto flutuante de 64 bits.
Funções
Python:
def somar(a, b):
return a + b
resultado = somar(3, 5)
print(resultado)
Zig:
const std = @import("std");
fn somar(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
const resultado = somar(3, 5);
std.debug.print("Resultado: {}\n", .{resultado});
}
Diferenças notáveis:
- Em Zig, os tipos dos parâmetros e do retorno são obrigatórios.
- Não existe
print()global; usamosstd.debug.print. - Todo programa Zig precisa de uma função
main. - A sintaxe
.{resultado}é uma tupla anônima usada para formatação.
Loops e Iteração
Python é famoso por seus loops elegantes com for...in. Zig tem uma abordagem diferente, mas igualmente poderosa.
Python - iterando sobre uma lista:
frutas = ["maçã", "banana", "laranja"]
for fruta in frutas:
print(fruta)
for i in range(10):
print(i)
Zig - iterando sobre um array:
const std = @import("std");
pub fn main() void {
const frutas = [_][]const u8{ "maçã", "banana", "laranja" };
for (frutas) |fruta| {
std.debug.print("{s}\n", .{fruta});
}
// Equivalente ao range(10)
var i: u32 = 0;
while (i < 10) : (i += 1) {
std.debug.print("{}\n", .{i});
}
}
Em Zig, o for itera sobre slices e arrays com uma sintaxe de captura usando |variavel|. Para loops numéricos, usamos while com uma cláusula de continuação.
Strings e Texto
O tratamento de strings é uma das maiores diferenças entre Python e Zig.
Python:
texto = "Olá, mundo!"
print(len(texto))
print(texto.upper())
print(texto[0:3])
partes = texto.split(", ")
Zig:
const std = @import("std");
pub fn main() void {
const texto = "Olá, mundo!";
// Comprimento em bytes (não caracteres Unicode)
std.debug.print("Tamanho: {}\n", .{texto.len});
// Zig não tem .upper() embutido
// Strings são slices de bytes: []const u8
// Fatiamento (slice)
const fatia = texto[0..5];
std.debug.print("Fatia: {s}\n", .{fatia});
}
Em Python, strings são objetos ricos com dezenas de métodos. Em Zig, strings são simplesmente slices de bytes ([]const u8). Isso significa que operações como upper(), split() e replace() precisam ser feitas manualmente ou usando funções da biblioteca padrão como std.mem. Para entender melhor como Zig lida com texto e coleções, confira o tutorial sobre strings e arrays em Zig.
Essa abordagem pode parecer primitiva inicialmente, mas garante zero alocações ocultas e controle total sobre a memória.
Tratamento de Erros: Exceções vs Error Unions
Uma das diferenças mais elegantes entre Python e Zig está no tratamento de erros.
Python:
def dividir(a, b):
if b == 0:
raise ValueError("Divisão por zero!")
return a / b
try:
resultado = dividir(10, 0)
except ValueError as e:
print(f"Erro: {e}")
Zig:
const std = @import("std");
const MathError = error{
DivisaoPorZero,
};
fn dividir(a: f64, b: f64) MathError!f64 {
if (b == 0.0) return MathError.DivisaoPorZero;
return a / b;
}
pub fn main() void {
const resultado = dividir(10.0, 0.0) catch |err| {
std.debug.print("Erro: {}\n", .{err});
return;
};
std.debug.print("Resultado: {d}\n", .{resultado});
}
Em Zig, erros não são exceções que “voam” pela pilha de chamadas. Eles são valores de retorno explícitos usando error unions (MathError!f64). Isso significa que o tipo de retorno MathError!f64 pode ser um f64 válido OU um erro do tipo MathError.
A vantagem? Você nunca é surpreendido por uma exceção não tratada. O compilador exige que todo erro seja explicitamente tratado com catch ou propagado com try.
Listas vs Arrays e Slices
Python - listas dinâmicas:
numeros = [1, 2, 3, 4, 5]
numeros.append(6)
numeros.pop()
filtrados = [x for x in numeros if x > 2]
Zig - arrays e ArrayList:
const std = @import("std");
pub fn main() !void {
// Array de tamanho fixo (como uma tupla fixa em Python)
const fixo = [_]i32{ 1, 2, 3, 4, 5 };
_ = fixo;
// ArrayList (equivalente a list do Python)
var lista = std.ArrayList(i32).init(std.heap.page_allocator);
defer lista.deinit(); // Libera memória automaticamente ao sair do escopo
try lista.append(1);
try lista.append(2);
try lista.append(3);
// Iteração
for (lista.items) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
A palavra-chave defer é extremamente útil: ela garante que lista.deinit() será chamado quando o bloco atual terminar, similar a um context manager (with) em Python.
Classes e Objetos vs Structs
Python é orientado a objetos. Zig não tem classes, herança ou polimorfismo de subtipos. Em vez disso, usa structs com métodos.
Python:
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def area(self):
return self.largura * self.altura
def __str__(self):
return f"Retângulo({self.largura}x{self.altura})"
r = Retangulo(5, 3)
print(r.area())
print(r)
Zig:
const std = @import("std");
const Retangulo = struct {
largura: f64,
altura: f64,
pub fn area(self: Retangulo) f64 {
return self.largura * self.altura;
}
pub fn formato(self: Retangulo, buf: []u8) []u8 {
return std.fmt.bufPrint(buf, "Retângulo({d}x{d})", .{
self.largura,
self.altura,
}) catch "erro";
}
};
pub fn main() void {
const r = Retangulo{
.largura = 5.0,
.altura = 3.0,
};
std.debug.print("Área: {d}\n", .{r.area()});
}
Em Zig, structs são tipos de valor (não referência como objetos Python). Não existe self implícito; o parâmetro self é explícito na assinatura do método. Campos são inicializados usando a sintaxe .campo = valor.
Dicionários vs HashMap
Python:
capitais = {
"Brasil": "Brasília",
"Argentina": "Buenos Aires",
"Chile": "Santiago"
}
print(capitais["Brasil"])
capitais["Peru"] = "Lima"
Zig:
const std = @import("std");
pub fn main() !void {
var mapa = std.StringHashMap([]const u8).init(std.heap.page_allocator);
defer mapa.deinit();
try mapa.put("Brasil", "Brasília");
try mapa.put("Argentina", "Buenos Aires");
try mapa.put("Chile", "Santiago");
if (mapa.get("Brasil")) |capital| {
std.debug.print("Capital: {s}\n", .{capital});
}
try mapa.put("Peru", "Lima");
}
Note como em Zig precisamos alocar memória explicitamente e usar defer para liberar. Cada operação put pode falhar (retornando um erro), então usamos try.
Valores Opcionais: None vs Optional
Python:
def buscar_usuario(id):
if id == 42:
return {"nome": "Ana"}
return None
usuario = buscar_usuario(1)
if usuario is not None:
print(usuario["nome"])
Zig:
const std = @import("std");
const Usuario = struct {
nome: []const u8,
};
fn buscarUsuario(id: u32) ?Usuario {
if (id == 42) {
return Usuario{ .nome = "Ana" };
}
return null;
}
pub fn main() void {
if (buscarUsuario(42)) |usuario| {
std.debug.print("Nome: {s}\n", .{usuario.nome});
} else {
std.debug.print("Usuário não encontrado\n", .{});
}
}
O tipo ?Usuario é um optional: pode conter um Usuario ou null. A sintaxe if (valor) |captura| faz o “unwrap” seguro do optional, similar ao pattern matching.
Quando Usar Zig Junto com Python
Você não precisa abandonar Python para usar Zig. Na verdade, as duas linguagens se complementam muito bem:
- Módulos nativos: Escreva funções críticas em Zig e chame-as do Python usando
ctypesou criando extensões C. - Ferramentas de CLI: Use Zig para criar ferramentas de linha de comando ultra-rápidas que seu código Python invoca.
- Processamento de dados: Para pipelines de dados onde performance é crítica, Zig pode processar arquivos binários muito mais rápido.
- WebAssembly: Compile Zig para WASM e use no backend ou frontend do seu projeto Python.
Exemplo simples de integração via biblioteca compartilhada:
// lib.zig - compila para .so/.dll
export fn fibonacci(n: u32) u32 {
if (n <= 1) return n;
var a: u32 = 0;
var b: u32 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
# main.py
import ctypes
lib = ctypes.CDLL("./libfib.so")
lib.fibonacci.restype = ctypes.c_uint32
lib.fibonacci.argtypes = [ctypes.c_uint32]
resultado = lib.fibonacci(40)
print(f"Fibonacci(40) = {resultado}")
Para compilar a biblioteca compartilhada em Zig, usamos o sistema de build do Zig:
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const lib = b.addSharedLibrary(.{
.name = "fib",
.root_source_file = b.path("lib.zig"),
.target = b.standardTargetOptions(.{}),
.optimize = b.standardOptimizeOption(.{}),
});
b.installArtifact(lib);
}
Tabela de Referência Rápida
| Python | Zig | Notas |
|---|---|---|
list | std.ArrayList | ArrayList precisa de allocator |
dict | std.HashMap | HashMap precisa de allocator |
None | null | Tipo optional ?T |
try/except | catch/try | Error unions são tipos |
class | struct | Sem herança |
with (context manager) | defer | Executa ao sair do escopo |
range() | while com contador | Ou for com slice |
lambda | Não existe | Use funções normais |
*args, **kwargs | Comptime generics | Abordagem diferente |
import modulo | @import("modulo") | Sistema de módulos baseado em arquivos |
Dicas Práticas para a Transição
- Comece pelo compilador: O compilador Zig tem mensagens de erro excelentes. Leia cada mensagem com atenção.
- Pense em memória: Em Python, você nunca pensa em alocação. Em Zig, toda alocação é explícita. Use
deferpara não esquecer de liberar. - Abraçe a tipagem: Tipos explícitos podem parecer verbosos, mas pegam bugs em tempo de compilação que em Python só aparecem em produção.
- Use
comptime: O recurso de computação em tempo de compilação de Zig substitui muitas metaprogramações que você faria com decorators ou metaclasses em Python. - Pratique com projetos pequenos: Comece reescrevendo scripts Python simples em Zig para ganhar familiaridade.
Conclusão
A transição de Python para Zig não é sobre trocar uma linguagem pela outra, mas sobre expandir seu repertório. Python continua excelente para prototipagem e produtividade, enquanto Zig oferece controle e performance quando você precisa chegar mais perto do hardware. Dominar ambas as linguagens fará de você um desenvolvedor mais completo e versátil.