Optional em Zig — O que é e Como Usar

Optional em Zig — O que é e Como Usar

Definição

Um Optional em Zig é um tipo representado pelo operador ? que pode conter ou um valor válido do tipo T ou null. A sintaxe ?T declara um optional do tipo T. Diferentemente de linguagens como C onde qualquer ponteiro pode ser null, em Zig a possibilidade de nulidade é explícita no sistema de tipos.

Optionals eliminam uma das maiores fontes de bugs em programação: o acesso a valores nulos sem verificação.

Por que Optionals Importam

  1. Null safety: O compilador obriga você a verificar se o valor existe antes de usá-lo.
  2. Expressividade: A assinatura da função comunica claramente que “pode não haver valor”.
  3. Zero custo: Para ponteiros, ?*T tem o mesmo tamanho que *T (null é representado como 0).
  4. Diferente de erro: Optional indica ausência, não falha. Use Error Union quando houver motivo da falha.

Exemplo Prático

Uso Básico

const std = @import("std");

fn encontrar(haystack: []const u8, needle: u8) ?usize {
    for (haystack, 0..) |char, indice| {
        if (char == needle) return indice;
    }
    return null; // Não encontrou
}

pub fn main() void {
    const texto = "Zig Brasil";

    if (encontrar(texto, 'B')) |indice| {
        std.debug.print("Encontrado na posição {}\n", .{indice});
    } else {
        std.debug.print("Não encontrado\n", .{});
    }
}

Unwrap com orelse

const valor: ?u32 = null;
const resultado = valor orelse 42; // resultado = 42

const outro: ?u32 = 10;
const resultado2 = outro orelse 42; // resultado2 = 10

Unwrap com if

fn processar(talvez_dados: ?[]const u8) void {
    if (talvez_dados) |dados| {
        // dados é []const u8 — desembrulhado, sem null
        std.debug.print("Dados: {s}\n", .{dados});
    } else {
        std.debug.print("Sem dados\n", .{});
    }
}

Unwrap com while

fn proximoValor(iterador: *Iterador) ?u32 {
    // retorna null quando acabar
}

// while com optional — itera até null
while (proximoValor(&iter)) |valor| {
    std.debug.print("{}\n", .{valor});
}

Optional de Ponteiro

const Node = struct {
    valor: i32,
    proximo: ?*Node, // Ponteiro que pode ser null
};

fn ultimo(node: *Node) *Node {
    var atual = node;
    while (atual.proximo) |prox| {
        atual = prox;
    }
    return atual;
}

Encadeamento com .?

const config: ?*Config = obterConfig();
// .? é equivalente a unwrap forçado (panic se null)
const porta = config.?.porta;

Cuidado: .? causa panic se o valor for null. Prefira if ou orelse.

Optional vs Error Union

Característica?T (Optional)!T (Error Union)
SignificaValor pode estar ausenteOperação pode falhar
Informação extraNenhuma (só null)Qual erro ocorreu
Operadororelsecatch
ExemploBusca em listaLeitura de arquivo

Armadilhas Comuns

  • Usar .? sem verificação: O unwrap forçado (.?) causa panic se o valor for null. Use if ou orelse para segurança.
  • Confundir ?T com !T: Optional indica ausência; error union indica falha com motivo. Escolha o tipo correto.
  • Optionals aninhados: ??T é válido mas raramente útil e pode causar confusão. Repense o design se chegar nesse ponto.
  • Usar optional quando um valor default é melhor: Se sempre há um valor razoável de fallback, considere usar o valor diretamente em vez de optional.

Boas Práticas com Optionals

Prefira if ou orelse ao .?: O unwrap forçado (.?) é conveniente mas perigoso. Reserve-o para casos onde o valor null é genuinamente impossível (similar ao unreachable).

Escolha entre ?T e !T com cuidado: Se a ausência de um valor é uma situação normal esperada (busca em mapa, índice não encontrado), use ?T. Se a ausência indica uma falha que o chamador precisa tratar (arquivo não existe, permissão negada), use !T.

// BOM: busca pode não encontrar nada — ausência é esperada
fn buscarNoCachê(chave: []const u8) ?[]const u8 { ... }

// BOM: leitura de arquivo pode falhar — falha precisa de diagnóstico
fn lerArquivo(path: []const u8) ![]u8 { ... }

Optional em structs para campos opcionais:

const Usuario = struct {
    nome: []const u8,
    email: []const u8,
    avatar_url: ?[]const u8 = null, // Nem todo usuário tem avatar
    ultimo_acesso: ?i64 = null,     // Pode nunca ter acessado
};

fn exibirPerfil(usuario: Usuario) void {
    std.debug.print("Nome: {s}\n", .{usuario.nome});
    if (usuario.avatar_url) |url| {
        std.debug.print("Avatar: {s}\n", .{url});
    }
}

Comparação com Outras Linguagens

Em C e C++, qualquer ponteiro pode ser null sem aviso — o programador precisa lembrar de checar. Em Java, todos os tipos de referência podem ser null, gerando NullPointerException em runtime. Em Kotlin e Swift, existe o conceito de “nullable” explícito com ? (similar ao Zig). Em Rust, o tipo Option<T> é equivalente direto. A diferença do Zig é que optionals de ponteiro (?*T) não têm overhead: null é literalmente o endereço zero, sem campo adicional.

Termos Relacionados

Tutoriais Relacionados

Continue aprendendo Zig

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