Unexpected Zig behaviour

(Edit: First reply below contains a shorter error-example)
Hey everyone, just got bitten by what I think is a nasty bug in the compiler. However I thought I should check here first in case this is intended behavior. The following code

const print = @import("std").debug.print;

pub fn do_stuff(slice: []?usize, idx: usize, cond: bool) ?usize {
    //const copy = if (cond) slice[idx] else return null;    
    const copy = slice[idx]; _ = cond;                                             
    print("\ncopy value before: {any}\tcopy type: {}\n", .{ copy, @TypeOf(copy) });  
    slice[idx] = null;                                                              
    print("copy value after: {any}\tcopy type: {}\n", .{ copy, @TypeOf(copy) });     
                                                                                    
    return copy;                                                                    
} 

pub fn main() !void {
    var arr = [_]?usize{ 1, 2, 3 };
    _ = do_stuff(&arr, 1, true);
}

Gives the expected output
copy value before: 2 copy type: ?usize
copy value after: 2 copy type: ?usize

However if we uncomment and swap the declerations of copy so that we get:

pub fn do_stuff(slice: []?usize, idx: usize, cond: bool) ?usize {
    const copy = if (cond) slice[idx] else return null;    
    //const copy = slice[idx]; _ = cond;                                             
...

The output is now instead
copy value before: 2 copy type: ?usize
copy value after: null copy type: ?usize

This can’t be right? Compiler version: 0.12.0-dev.1849+bb0f7d55e (same behaviour on version 0.11.0).

Godbolt link to an example that can be run: [Compiler Explorer]. If people agree this is a bug I can open an issue.

2 Likes

Found a smaller error-example (also print the addresses of each variable):

const print = @import("std").debug.print;

pub fn do_stuff(cond: bool) ?usize {            
    var us: ?usize = 1;
    const copy = if (cond) us else return null;                                                                
    print("\ncopy value before: {any}\tcopy address: {*}\tus address: {*}\n", .{ copy, &copy, &us });
    us = 3;                                                                    
    print("copy value after: {any}\tcopy address: {*}\tus address: {*}\n", .{ copy, &copy, &us }); 
                                                                                    
    return copy;                                                                     
}                       
pub fn main() !void {           
    _ = do_stuff(true);
}

Link to runnable example: [Compiler Explorer]

Edit: After playing around a bit it seems to me for the entire scope of do_stuff assigning to us immediately changes the observable value in copy. It seems like we’ve hit undefined behavior, but is there anything in the code that justifiably causes undefined behavior?

Seems like this bug is already mentioned in Inline if for variable bool value makes the result a reference/pointer to the data · Issue #16007 · ziglang/zig · GitHub

2 Likes