@addWithOverflow em Zig
O @addWithOverflow realiza uma adição e indica se houve overflow. Retorna uma tupla com o resultado (possivelmente truncado) e um bit de overflow. Isso permite tratar overflow explicitamente em vez de causar panic (modo safe) ou undefined behavior (modo unsafe).
Sintaxe
@addWithOverflow(a: T, b: T) struct { T, u1 }
Parâmetros
- a (
T): Primeiro operando (tipo inteiro). - b (
T): Segundo operando (mesmo tipo inteiro).
Valor de retorno
Retorna uma tupla struct { T, u1 }:
- [0] (
T): Resultado da adição (truncado ao tamanho do tipo se houve overflow). - [1] (
u1):1se houve overflow,0caso contrário.
Exemplos práticos
Exemplo 1: Detecção básica de overflow
const std = @import("std");
pub fn main() void {
// Sem overflow
const r1 = @addWithOverflow(@as(u8, 100), @as(u8, 50));
std.debug.print("100 + 50 = {} (overflow: {})\n", .{ r1[0], r1[1] });
// 100 + 50 = 150 (overflow: 0)
// Com overflow: 200 + 100 = 300, mas u8 vai até 255
const r2 = @addWithOverflow(@as(u8, 200), @as(u8, 100));
std.debug.print("200 + 100 = {} (overflow: {})\n", .{ r2[0], r2[1] });
// 200 + 100 = 44 (overflow: 1) — 300 mod 256 = 44
}
Exemplo 2: Adição segura com tratamento de erro
const std = @import("std");
fn somaSegura(a: u32, b: u32) !u32 {
const resultado = @addWithOverflow(a, b);
if (resultado[1] != 0) {
return error.Overflow;
}
return resultado[0];
}
pub fn main() void {
const a = somaSegura(1_000_000, 2_000_000) catch |err| {
std.debug.print("Erro: {}\n", .{err});
return;
};
std.debug.print("Resultado: {}\n", .{a}); // 3000000
const b = somaSegura(std.math.maxInt(u32), 1) catch |err| {
std.debug.print("Erro: {s}\n", .{@errorName(err)}); // Overflow
return;
};
_ = b;
}
Exemplo 3: Acumulador com detecção de limite
const std = @import("std");
fn somarArray(valores: []const u16) struct { soma: u16, overflow: bool } {
var total: u16 = 0;
var teve_overflow = false;
for (valores) |v| {
const r = @addWithOverflow(total, v);
total = r[0];
if (r[1] != 0) teve_overflow = true;
}
return .{ .soma = total, .overflow = teve_overflow };
}
pub fn main() void {
const dados = [_]u16{ 10000, 20000, 30000, 10000 };
const resultado = somarArray(&dados);
if (resultado.overflow) {
std.debug.print("AVISO: overflow detectado!\n", .{});
}
std.debug.print("Soma (possivelmente truncada): {}\n", .{resultado.soma});
}
Casos de uso comuns
- Aritmética segura: Detectar overflow sem causar panic.
- Criptografia: Operações que precisam de wrap-around explícito.
- Validação de entrada: Verificar se somas de valores do usuário excedem limites.
- Contadores: Detectar quando um contador atinge o valor máximo.
Comportamento por modo de compilação
O @addWithOverflow se comporta de forma idêntica em todos os modos (Debug, ReleaseSafe, ReleaseFast, ReleaseSmall): sempre retorna o resultado truncado e o bit de overflow. Não há panic, não há undefined behavior.
Isso o diferencia do operador + simples:
| Modo | Operador + com overflow | @addWithOverflow |
|---|---|---|
| Debug | panic em runtime | resultado + bit 1 |
| ReleaseSafe | panic em runtime | resultado + bit 1 |
| ReleaseFast | undefined behavior | resultado + bit 1 |
| ReleaseSmall | undefined behavior | resultado + bit 1 |
Equivalente em C
Em C, detectar overflow de soma com segurança é mais verboso:
#include <stdint.h>
#include <stdbool.h>
// Método 1: usando __builtin_add_overflow (GCC/Clang)
bool soma_segura_c(uint32_t a, uint32_t b, uint32_t *resultado) {
return __builtin_add_overflow(a, b, resultado);
}
// Método 2: comparação manual
bool soma_segura_manual(uint32_t a, uint32_t b, uint32_t *resultado) {
*resultado = a + b;
return *resultado < a; // overflow se resultado menor que operando
}
Em Zig, @addWithOverflow é mais conciso e seguro por padrão.
Erros comuns
Ignorar o bit de overflow: O retorno mais comum é usar apenas r[0] sem verificar r[1]. Isso derrota o propósito do builtin.
// ERRADO: ignora o overflow
const soma = @addWithOverflow(a, b)[0];
// CORRETO: verificar o overflow
const r = @addWithOverflow(a, b);
if (r[1] != 0) return error.Overflow;
const soma = r[0];
Confundir com wrapping arithmetic: Se você quer soma que wraps sem erro, use o operador +%:
const wrapped = a +% b; // wrap intencional, sem verificação
const checked = @addWithOverflow(a, b); // resultado + flag
Perguntas Frequentes
Por que o resultado é truncado e não zero/máximo em caso de overflow?
O Zig retorna o resultado módulo 2^N (onde N é a largura do tipo) por consistência com a aritmética de inteiros e para permitir que o chamador decida o que fazer. Às vezes o valor truncado é útil (em hashing ou criptografia, por exemplo).
Posso usar @addWithOverflow com tipos signed?
Sim. O builtin funciona com i8, i16, i32, i64, etc. O overflow signed também é detectado corretamente — por exemplo, @addWithOverflow(@as(i8, 127), @as(i8, 1)) retorna {-128, 1}.
@addWithOverflow tem custo em performance?
Em arquiteturas modernas (x86-64, ARM64), o compilador mapeia para instruções que definem a flag de carry/overflow automaticamente. O custo é geralmente nulo ou mínimo — uma instrução ADD seguida de leitura da flag.
Builtins relacionados
- @subWithOverflow — Subtração com detecção de overflow
- @mulWithOverflow — Multiplicação com detecção de overflow
- @shlExact — Shift left exato
- @min / @max — Limitar valores