@addWithOverflow em Zig — Referência e Exemplos

@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): 1 se houve overflow, 0 caso 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

  1. Aritmética segura: Detectar overflow sem causar panic.
  2. Criptografia: Operações que precisam de wrap-around explícito.
  3. Validação de entrada: Verificar se somas de valores do usuário excedem limites.
  4. 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:

ModoOperador + com overflow@addWithOverflow
Debugpanic em runtimeresultado + bit 1
ReleaseSafepanic em runtimeresultado + bit 1
ReleaseFastundefined behaviorresultado + bit 1
ReleaseSmallundefined behaviorresultado + 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

Tutoriais relacionados

Continue aprendendo Zig

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