You are relying on unchecked illegal behavior, so this code isn’t valid and the bugs are your own.
Consider this modification to your program:
const std = @import("std");
pub fn main() !void {
var a: ?u32 = @as(?u32, undefined);
std.debug.print("{X}\n", .{std.mem.asBytes(&a)});
const b = &a.?;
b.* = 0x1234;
std.debug.print("{X}\n", .{std.mem.asBytes(&a)});
std.debug.print("{?x}\n", .{a});
}
This is the output with Debug.
AAAAAAAAAAAAAAAA
34120000AAAAAAAA
1234
and here it is with release fast:
0000000000000000
3412000000000000
null
So what’s going on here? The definition of “undefined” is that it can take on any value. Almost always, this means that you should never read an undefined value, only later write to it before any reads take place. In safe modes, Zig will write hex ‘AA’ over any memory set to undefined, as a marker for the programmer doing the debugging that if you’re looking at that, it’s /probably/ undefined memory. In release mode, this overwriting doesn’t happen.
So in your code, you’re setting the whole optional to undefined. In memory, the u32 comes first, and the flag for whether the optional is set to a value comes second. The actual illegal code is const b = &a.?;. In Debug, it will check that the optional is set, which reading the flag, it happens to see a non-zero value, and thinks it is. In ReleaseFast, it will doesn’t safety check the presence flag, which happens to default to zero’d memory, but wouldn’t always.
I suspect that what you intended to do was:
const std = @import("std");
pub fn main() !void {
var a: ?u32 = @as(u32, undefined);
std.debug.print("{X}\n", .{std.mem.asBytes(&a)});
const b = &a.?;
b.* = 0x1234;
std.debug.print("{X}\n", .{std.mem.asBytes(&a)});
std.debug.print("{?x}\n", .{a});
}
Which in Debug prints:
0000000001000000
3412000001000000
1234
and in ReleaseFast prints:
0000000001000000
3412000001000000
1234