@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
- Bit flags: Criar flags de bit com garantia de não overflow.
- Multiplicação por potência de 2: Shift como multiplicação verificada.
- Protocolos binários: Montar campos de bits com garantia de integridade.
- 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
- @shrExact — Shift right exato
- @addWithOverflow — Adição com detecção de overflow
- @clz — Contar zeros à esquerda
- @popCount — Contar bits em 1