zig
zig copied to clipboard
Inline if for variable bool value makes the result a reference/pointer to the data
Zig Version
0.11.0-dev.3395+1e7dcaa3a
Steps to Reproduce and Observed Behavior
When we use an inline if, the result value is a pointer to the data.
So if we say
var condition1 = true;
const ans1 = if (condition1) answers[0] else answers[1]
then ans1
will be a reference to answers[0]
, and if we change answers[0]
, then value in ans1
will also change.
This does not happen if condition1
is declared as const
.
I initially caught the bug in version 2939, with variable optional types, but that has been fixed since.
Reproducible example:
const std = @import("std");
const Block = struct {
value: u8,
};
pub fn main() !void {
var blocks: [16]Block = undefined;
{
blocks[0] = .{ .value = 0 };
// using variable optionals and bools
var variable_opt_index: ?usize = 0;
var variable_bool: bool = true;
const variable_opt_index_block = if (variable_opt_index) |i| blocks[i] else blocks[1];
const variable_bool_index_block: Block = if (variable_bool) blocks[0] else blocks[1];
// using constant optionals and bools
const const_opt_index: ?usize = 0;
const const_bool: bool = true;
const const_opt_index_block = if (const_opt_index) |i| blocks[i] else blocks[1];
const const_bool_index_block = if (const_bool) blocks[0] else blocks[1];
const opt_value = blocks[0].value;
// changing the underlying value
blocks[0].value = 42;
std.debug.print("opt_value = {d}\n", .{opt_value});
std.debug.print("variable_opt_index_block.value = {d} // should be 0\nvariable_bool_index_block.value = {d} // should be 0\n", .{
variable_opt_index_block.value, variable_bool_index_block.value,
});
std.debug.print("const_opt_index_block.value = {d}\nconst_bool_index_block.value = {d}\n", .{
const_opt_index_block.value, const_bool_index_block.value,
});
}
}
Output:
opt_value = 0
variable_opt_index_block.value = 0 // should be 0
variable_bool_index_block.value = 42 // should be 0
const_opt_index_block.value = 0
const_bool_index_block.value = 0
Expected Behavior
The value returned should not be a reference. So changing the underlying data should not change the value stored.
Expected output for the above code:
opt_value = 0
variable_opt_index_block.value = 0 // should be 0
variable_bool_index_block.value = 0 // should be 0
const_opt_index_block.value = 0
const_bool_index_block.value = 0
wierd. here's a reduction in the form of a failing test:
// /tmp/tmp.zig
const std = @import("std");
const S = struct { value: u8 };
test {
var buf: [16]S = undefined;
buf[0] = .{ .value = 0 };
var x = true;
const s = if (x) buf[0] else buf[1];
buf[0].value = 42;
try std.testing.expectEqual(@as(u8, 0), s.value);
}
$ zig test /tmp/tmp.zig
Test [1/1] test_0... expected 0, found 42
Test [1/1] test_0... FAIL (TestExpectedEqual)
/media/data/Users/Travis/Documents/Code/zig/zig/lib/std/testing.zig:78:17: 0x20f19e in expectEqual__anon_1096 (test)
return error.TestExpectedEqual;
^
/tmp/tmp.zig:13:5: 0x20f41e in test_0 (test)
try std.testing.expectEqual(@as(u8, 0), s.value);
^
0 passed; 0 skipped; 1 failed.
I have seen this behavior while working on the compiler, and I had to use var
instead of const
to force a copy to be made, even though I never needed to modify the copy.
I am currently using the fix mentioned by @jacobly0 to get around the issue.
Though I am not able to create a small reproducible test case, I am facing this same issue with optional values in my codebase.
Just got bit by this bug, quite nasty as it presents very similar to undefined behavior.
I initially caught the bug in version 2939, with variable optional types, but that has been fixed since.
Doesn't seem like it, here's a minimal reproducible example
const expect = @import("std").testing.expect;
pub fn do_stuff(cond: bool) ?usize {
var opt: ?usize = 1;
const copy = if (cond) opt else return null;
opt = null;
return copy;
}
pub fn main() !void {
try expect(do_stuff(true).? == 1);
}
(link to a runnable example, we expect the test in main
to pass. Also it doesn't matter what value you assign to opt
, copy
ends up taking on the same value. Version 0.12.0-dev.1849+bb0f7d55e)
Also take a look at this comment here. Most notably, copy
in the above snippet does not have the same address as opt
, yet assigning to opt
changes the observable value in copy
.