@mod with negative const value hits unreachable?

Hi, I’m new to Zig and trying to understand this.

This version crashes:

const std = @import("std");

pub fn main() void {
    const num = -2; // -4, -6, ...

    std.debug.print("{}\n", .{@mod(num, 2)});

    switch (@mod(num, 2)) {
        0 => std.debug.print("Even\n", .{}),
        1 => std.debug.print("Odd\n", .{}),
        else => unreachable,
    }
}

Output:

0
thread panic: reached unreachable code

What I’m seeing:

  • Negative even const values → prints 0 but then crashes
  • Negative odd values (like -11 ) → works (Odd )
  • All non-negative values I tested → work fine

If I change const to var , it works:

const std = @import("std");

pub fn main() void {
    var num: i32 = -2;
    _ = #

    std.debug.print("{}\n", .{@mod(num, 2)});

    switch (@mod(num, 2)) {
        0 => std.debug.print("Even\n", .{}),
        1 => std.debug.print("Odd\n", .{}),
        else => unreachable,
    }
}

Output:

0
Even

Zig version: 0.16.0

Am I misunderstanding something about @mod or switch with const , or is this unexpected behavior?

Small update: this isn’t just % 2.

I’m seeing the same crash for other divisors when the value is a negative multiple.

Example:

const std = @import("std");

pub fn main() void {
    const num = -6;

    std.debug.print("{}\n", .{@mod(num, 3)});

    switch (@mod(num, 3)) {
        0 => std.debug.print("0\n", .{}),
        1 => std.debug.print("1\n", .{}),
        2 => std.debug.print("2\n", .{}),
        else => unreachable,
    }
}

Output:

0
thread panic: reached unreachable code

So pattern seems to be:

  • negative multiples of the divisor (like -6 % 3, -4 % 2) → crash
  • others work fine
  • switching constvar makes it work

Does this help narrow it down?

#36451

1 Like

tried something along those lines, but this seems to contradict the “negative 0” idea.

const std = @import("std");

pub fn main() void {
    const num = -2;

    std.debug.print("{}\n", .{@mod(num, 2)});

    switch (@mod(num, 2)) {
        0...1 => std.debug.print("fine\n", .{}),
        else => unreachable,
    }
}

Output:

0
fine

Here it still prints 0 , but the switch works fine when I use a range (0...1 ).

So it doesn’t look like the value itself is different (like a “negative 0”), but something about how the exact 0 case is handled?

Without the PR I linked, the compiler saves positive zero and negative zero as two different internal values. Mostly these two values act the same way, but when using a switch prong which is not a range, the compiler uses this compare function, if the switch condition is comptime known. This function checks if the compiler internal values are the same.

Because both values represent 0, they act in the same way when you print them.

When using a range (and the condition is comptime known), the compiler uses the bigint order function to check if the switch condition is in the range. This already handles the -0 case.

3 Likes