@shlExact em Zig — Referência e Exemplos

@shlExact em Zig

O @shlExact realiza um shift left (deslocamento à esquerda) e garante que nenhum bit significativo é perdido (descartado pelo topo). Se algum bit 1 for deslocado para fora, causa panic em modo safe ou undefined behavior em release. É a versão “exata” do shift, útil quando o overflow de bits não é aceitável.

Sintaxe

@shlExact(value: T, shift_amt: Log2T) T

Parâmetros

  • value (T): Valor inteiro a ser deslocado.
  • shift_amt (Log2T): Número de posições para deslocar (tipo log2 do tamanho de T).

Valor de retorno

Retorna T — o resultado do shift left, garantindo que nenhum bit foi perdido.

Exemplos práticos

Exemplo 1: Shift exato seguro

const std = @import("std");

pub fn main() void {
    // Shift seguro: 1 << 4 = 16 (cabe em u8)
    const a: u8 = @shlExact(@as(u8, 1), 4);
    std.debug.print("1 << 4 = {}\n", .{a}); // 16

    // Shift seguro: 3 << 2 = 12
    const b: u8 = @shlExact(@as(u8, 3), 2);
    std.debug.print("3 << 2 = {}\n", .{b}); // 12

    // Isso causaria panic em safe mode:
    // @shlExact(@as(u8, 1), 8) — deslocaria o bit 1 para fora!
}

Exemplo 2: Construir flags com shift exato

const std = @import("std");

const Permissao = struct {
    pub fn flag(comptime bit: u5) u32 {
        return @shlExact(@as(u32, 1), bit);
    }
};

pub fn main() void {
    const LEITURA = Permissao.flag(0);    // 1
    const ESCRITA = Permissao.flag(1);    // 2
    const EXECUCAO = Permissao.flag(2);   // 4

    const permissoes = LEITURA | ESCRITA | EXECUCAO;
    std.debug.print("Permissões: 0b{b:0>3}\n", .{permissoes}); // 0b111
}

Exemplo 3: Multiplicação por potência de 2 verificada

const std = @import("std");

fn multiplicarPor2(valor: u16, potencia: u4) !u16 {
    // Usar @shlExact em vez de << para detectar overflow
    return @shlExact(valor, potencia);
}

test "multiplicar por potência de 2" {
    try std.testing.expect(multiplicarPor2(10, 3) catch unreachable == 80);
    try std.testing.expect(multiplicarPor2(1, 15) catch unreachable == 32768);
}

Casos de uso comuns

  1. Bit flags: Criar flags de bit com garantia de não overflow.
  2. Multiplicação por potência de 2: Shift como multiplicação verificada.
  3. Protocolos binários: Montar campos de bits com garantia de integridade.
  4. Criptografia: Operações de bit que não devem perder informação.

Diferença entre @shlExact e o operador <<

O operador << padrão em Zig tem comportamento diferente conforme o modo de compilação:

  • Debug/ReleaseSafe: panic se bits significativos forem perdidos.
  • ReleaseFast/ReleaseSmall: undefined behavior silencioso se bits forem perdidos.

O @shlExact garante que bits significativos não são perdidos independente do modo. Se houver perda de bits, o resultado é sempre panic em safe ou undefined behavior documentado em fast. A semântica é mais explícita: o nome “Exact” comunica claramente a intenção de que o shift não deve descartar informação.

Comparação com equivalente em C

Em C, shift left não tem verificação de overflow de bits:

// C: shift silencioso, pode perder bits sem aviso
unsigned char resultado = 200 << 2; // bits perdidos silenciosamente

// C: verificação manual necessária (verbosa e propensa a erros)
unsigned char valor = 200;
unsigned char shift = 2;
if (valor > (255 >> shift)) {
    // overflow de bits
}
unsigned char resultado = valor << shift;

Em Zig, @shlExact documenta a intenção e fornece verificação automática:

// Zig: explícito e verificado
const resultado = @shlExact(@as(u8, 3), 2); // seguro: 12
// @shlExact(@as(u8, 200), 2) causaria panic — bits seriam perdidos

Casos de uso avançados

Construção de máscaras de bits em comptime

@shlExact é especialmente valioso em comptime para validar constantes:

const std = @import("std");

// Garante em comptime que a flag cabe no tipo
fn criarFlag(comptime bit: u5) u32 {
    return @shlExact(@as(u32, 1), bit);
}

// Erro de compilação se bit >= 32
const FLAG_ULTIMO: u32 = criarFlag(31); // ok: 0x8000_0000
// const FLAG_INVALIDO: u32 = criarFlag(32); // erro de compilação!

Multiplicação verificada por potência de 2 em protocolos

fn tamanhoBloco(comptime ordem: u4) u16 {
    // Shift exato garante que o tamanho não transborda u16
    return @shlExact(@as(u16, 1), ordem);
}

const BLOCO_512: u16 = tamanhoBloco(9);   // 512
const BLOCO_4K: u16 = tamanhoBloco(12);  // 4096
const BLOCO_32K: u16 = tamanhoBloco(15); // 32768

Erros comuns

1. Confundir @shlExact com shift normal para multiplicação:

// Se apenas quer multiplicar por 2 e não se importa com overflow:
const resultado = valor << 1; // OK para casos sem verificação

// Use @shlExact somente quando perder bits é um bug:
const resultado = @shlExact(valor, 1); // panic se bit mais significativo for 1

2. Shift amount do tipo errado: O segundo parâmetro deve ser do tipo Log2(N) para um tipo de N bits. Para u32, o shift amount deve ser u5 (0..31). Passar um valor fora do intervalo causa erro de compilação.

Perguntas Frequentes

P: Qual é a diferença entre @shlExact e @mulWithOverflow para potências de 2?

R: Ambos detectam overflow, mas por mecanismos diferentes. @shlExact verifica que nenhum bit é perdido pelo topo. @mulWithOverflow verifica que o resultado cabe no tipo. Para multiplicação por potência de 2, são equivalentes em resultado, mas @shlExact é mais eficiente (uma instrução de shift vs. multiplicação).

P: @shlExact funciona com tipos signed?

R: Sim. Para tipos signed, a semântica é a mesma: se qualquer bit 1 for deslocado para fora (incluindo bits que alterariam o sinal), ocorre panic em safe mode.

Builtins relacionados

Tutoriais relacionados

Continue aprendendo Zig

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