Guia de Migração: C++ para Zig

Introdução

Migrar de C++ para Zig é uma transformação significativa. C++ é uma linguagem complexa com herança, templates, exceções, RAII via destrutores, e uma Standard Template Library rica. Zig substitui toda essa complexidade por um conjunto menor de primitivas mais poderosas.

Este guia cobre a conversão dos padrões mais comuns de C++ para Zig. Para comparação detalhada, veja Zig vs C++.

Pré-requisitos

Classes para Structs

C++

class Vetor3D {
private:
    double x_, y_, z_;
public:
    Vetor3D(double x, double y, double z) : x_(x), y_(y), z_(z) {}

    double magnitude() const {
        return std::sqrt(x_*x_ + y_*y_ + z_*z_);
    }

    Vetor3D operator+(const Vetor3D& outro) const {
        return Vetor3D(x_ + outro.x_, y_ + outro.y_, z_ + outro.z_);
    }
};

Zig

const std = @import("std");

const Vetor3D = struct {
    x: f64,
    y: f64,
    z: f64,

    pub fn criar(x: f64, y: f64, z: f64) Vetor3D {
        return .{ .x = x, .y = y, .z = z };
    }

    pub fn magnitude(self: Vetor3D) f64 {
        return std.math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
    }

    pub fn somar(self: Vetor3D, outro: Vetor3D) Vetor3D {
        return .{
            .x = self.x + outro.x,
            .y = self.y + outro.y,
            .z = self.z + outro.z,
        };
    }
};

Em Zig, não há sobrecarga de operadores. Use métodos com nomes descritivos. Não há construtores — use funções estáticas como criar() ou init().

Templates para Comptime

C++

template<typename T>
class Pilha {
    std::vector<T> items;
public:
    void push(const T& item) { items.push_back(item); }
    T pop() {
        T item = items.back();
        items.pop_back();
        return item;
    }
    bool empty() const { return items.empty(); }
};

// Uso
Pilha<int> pilha;
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();
        }

        pub fn empty(self: Self) bool {
            return self.items.items.len == 0;
        }
    };
}

// Uso
var pilha = Pilha(i32).init(allocator);
defer pilha.deinit();
try pilha.push(42);

RAII e Destrutores para defer

C++

class Arquivo {
    FILE* handle;
public:
    Arquivo(const char* path) : handle(fopen(path, "r")) {
        if (!handle) throw std::runtime_error("Erro ao abrir");
    }
    ~Arquivo() {
        if (handle) fclose(handle);
    }
    // Move-only
    Arquivo(Arquivo&& other) noexcept : handle(other.handle) {
        other.handle = nullptr;
    }
};

void processar() {
    Arquivo f("dados.txt");  // destrutor chamado automaticamente
}

Zig

const Arquivo = struct {
    handle: std.fs.File,

    pub fn abrir(caminho: []const u8) !Arquivo {
        return .{
            .handle = try std.fs.cwd().openFile(caminho, .{}),
        };
    }

    pub fn fechar(self: *Arquivo) void {
        self.handle.close();
    }
};

fn processar() !void {
    var f = try Arquivo.abrir("dados.txt");
    defer f.fechar(); // equivalente ao destrutor
}

Em Zig, defer substitui RAII. É explícito — o programador decide quando e o que é liberado. errdefer executa apenas em caso de erro. Veja Padrões Errdefer.

Exceções para Error Unions

C++

double dividir(double a, double b) {
    if (b == 0) throw std::invalid_argument("Divisão por zero");
    return a / b;
}

try {
    double resultado = dividir(10, 0);
} catch (const std::invalid_argument& e) {
    std::cerr << e.what() << std::endl;
}

Zig

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

const resultado = dividir(10, 0) catch |err| {
    std.debug.print("Erro: {}\n", .{err});
    return;
};

Veja Error Sets Customizados e Error Logging.

Smart Pointers para Allocators

C++

auto ptr = std::make_unique<MinhaClasse>(args...);
auto shared = std::make_shared<MinhaClasse>(args...);
std::weak_ptr<MinhaClasse> weak = shared;

Zig

// unique_ptr equivalente
const ptr = try allocator.create(MinhaStruct);
defer allocator.destroy(ptr);

// Não há shared_ptr nativo — implementar se necessário
// ou usar arena allocator para gerenciamento em grupo
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();

Zig não tem smart pointers. O gerenciamento é via allocators e defer. Para compartilhamento, use arena allocators ou implemente contagem de referências manualmente. Veja ArenaAllocator.

STL Containers para std de Zig

C++ (STL)Zig (std)
std::vector<T>std.ArrayList(T)
std::array<T,N>[N]T
std::string[]const u8 / std.ArrayList(u8)
std::unordered_map<K,V>std.HashMap(K,V,...)
std::map<K,V>std.TreeMap (ou sorted array)
std::optional<T>?T
std::variant<A,B,C>union(enum) { a: A, b: B, c: C }

Herança para Composição

C++

class Forma {
public:
    virtual double area() const = 0;
    virtual ~Forma() = default;
};

class Circulo : public Forma {
    double raio;
public:
    Circulo(double r) : raio(r) {}
    double area() const override { return 3.14159 * raio * raio; }
};

Zig

// Opção 1: Comptime (polimorfismo estático)
fn calcularArea(forma: anytype) f64 {
    return forma.area();
}

const Circulo = struct {
    raio: f64,
    pub fn area(self: Circulo) f64 {
        return std.math.pi * self.raio * self.raio;
    }
};

// Opção 2: Interface manual (polimorfismo dinâmico)
const Forma = struct {
    ptr: *anyopaque,
    areaFn: *const fn (*anyopaque) f64,

    pub fn area(self: Forma) f64 {
        return self.areaFn(self.ptr);
    }
};

Namespaces

C++

namespace math {
    namespace linear {
        class Matrix { /* ... */ };
    }
}

Zig

// math/linear.zig
pub const Matrix = struct {
    // ...
};

// main.zig
const linear = @import("math/linear.zig");
const m = linear.Matrix{};

Zig usa o sistema de arquivos como namespaces. Cada arquivo é um struct implícito.

Testes

C++

// Com Google Test
TEST(CalculadoraTest, Soma) {
    EXPECT_EQ(soma(2, 3), 5);
}

Zig

test "soma" {
    try std.testing.expectEqual(@as(i32, 5), soma(2, 3));
}

Veja Testes Unitários Básicos e Testes com Allocator.

Conclusão

A migração de C++ para Zig simplifica o código eliminando camadas de abstração (herança, templates complexos, exceções). O resultado é código mais previsível, mais fácil de entender e debugar, com performance equivalente ou superior.

Para build system, veja Migrar de CMake para build.zig. Para a estratégia geral, consulte Guia de Migração: C para Zig (muitos padrões se aplicam).

Continue aprendendo Zig

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