Sentinel em Zig — O que é e Como Usar

Sentinel em Zig — O que é e Como Usar

Definição

Um sentinel-terminated array (array terminado por sentinela) em Zig é um array ou slice que possui um valor especial — o sentinel — imediatamente após o último elemento. O exemplo mais comum é a string terminada em zero ([:0]u8), equivalente às strings C (char* com \0 no final).

A sintaxe [N:s]T declara um array de N elementos do tipo T com sentinela s. Para slices, [:s]T indica um slice com sentinel.

Por que Sentinels Importam

  1. Interoperabilidade com C: APIs C esperam strings terminadas em null. Zig facilita a criação e uso desses tipos.
  2. Segurança de tipos: O tipo carrega a informação do sentinel, garantindo em tempo de compilação que o terminador está presente.
  3. Compatibilidade: Sentinel-terminated slices podem ser convertidos para slices normais sem custo.
  4. String literals: Todas as string literals em Zig são *const [N:0]u8 — terminadas em zero automaticamente.

Exemplo Prático

Strings Terminadas em Zero

const std = @import("std");

pub fn main() void {
    // String literal é automaticamente sentinel-terminated
    const mensagem: [:0]const u8 = "Olá, Zig!";

    // Acessar o sentinel
    std.debug.print("Último char: '{}'\n", .{mensagem[mensagem.len]}); // 0

    // Passar para função C
    _ = std.c.printf("%s\n", mensagem.ptr);
}

Arrays com Sentinel Customizado

// Array de 3 elementos terminado com 0xFF
const dados: [3:0xFF]u8 = .{ 10, 20, 30 };

// O elemento na posição [3] é 0xFF (o sentinel)
std.debug.print("Sentinel: {}\n", .{dados[3]}); // 255

Convertendo entre Tipos

const std = @import("std");

pub fn main() void {
    // Sentinel-terminated → slice normal (sem custo)
    const sentinel_slice: [:0]const u8 = "Zig Brasil";
    const slice_normal: []const u8 = sentinel_slice;
    _ = slice_normal;

    // Array → sentinel slice
    var array: [5:0]u8 = .{ 'H', 'e', 'l', 'l', 'o' };
    const s: [:0]u8 = &array;
    _ = s;
}

Interop com C

const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("string.h");
});

pub fn main() void {
    const zig_string: [:0]const u8 = "Texto do Zig";

    // strlen espera [*:0]const u8 — compatível automaticamente
    const tamanho = c.strlen(zig_string);
    std.debug.print("Tamanho via C: {}\n", .{tamanho});
}

Criando Sentinel Slice Dinamicamente

fn criarStringC(allocator: std.mem.Allocator, texto: []const u8) ![:0]u8 {
    const resultado = try allocator.allocSentinel(u8, texto.len, 0);
    @memcpy(resultado, texto);
    return resultado;
}

Tipos com Sentinel

TipoDescrição
[N:0]u8Array de N bytes + null terminator
[:0]u8Slice de bytes com null terminator
[*:0]u8Many-pointer terminado em null
[N:s]TArray de N elementos com sentinel s

Boas Práticas

  • Sempre use [:0]const u8 ao passar strings para C: Isso garante em tempo de compilação que o terminador zero está presente, eliminando a possibilidade de buffer overread no lado C.
  • Use allocSentinel para strings dinâmicas: allocator.allocSentinel(u8, len, 0) é a forma correta de alocar strings terminadas em null no heap.
  • Prefira []const u8 internamente: Internamente no código Zig, use slices normais. Converta para [:0] apenas no ponto de interop com C.
  • Conheça std.mem.sliceTo: std.mem.sliceTo(ptr, 0) converte um [*:0]u8 (ponteiro com sentinel) para um slice [:0]u8 calculando o comprimento ao encontrar o zero.

Como o Compilador Garante a Segurança

O tipo [:0]u8 carrega informação sobre o sentinel no sistema de tipos. Isso significa que passar um []u8 simples (sem sentinel garantido) para uma função que espera [:0]u8 é um erro de compilação. O programador é forçado a usar os tipos corretos, e o compilador verifica a presença do sentinel estaticamente sempre que possível.

Para string literals, o Zig automaticamente adiciona o byte zero após os dados, de modo que "hello" tem o tipo *const [5:0]u8 — um ponteiro para um array de 5 bytes com sentinel zero. Isso torna literals de string diretamente compatíveis com APIs C sem qualquer conversão ou cópia.

Armadilhas Comuns

  • Assumir que todo []const u8 é terminado em zero: Slices normais NÃO têm sentinel. Só [:0]const u8 garante o terminador.
  • Acessar além do sentinel: O sentinel está em slice[slice.len], mas acessar slice[slice.len + 1] é comportamento indefinido.
  • Esquecer de usar allocSentinel: Ao alocar memória para strings que serão passadas para C, use allocSentinel em vez de alloc.
  • Converter sem cuidado: Converter []u8 para [:0]u8 requer que o sentinela realmente exista na memória. Use std.mem.sliceTo para conversão segura.

Termos Relacionados

  • Slice — Referência a sequência de memória
  • Pointer Types — Tipos de ponteiro em Zig
  • usize — Tipo de índice e comprimento

Tutoriais Relacionados

Continue aprendendo Zig

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