About sub-expression evaluation order rules

What are rules of sub-expression evaluation order? It looks the rule is by the order of places in code, right?

const std = @import("std");

const T = struct {
    a: bool,
    b: bool,
};

var t1: T = .{
    .a = true,
    .b = false,
};

var t2: T = .{
    .a = true,
    .b = false,
};

pub fn main() !void {
    t1 = .{
        .a = false,
        .b = t1.a, // t1.a is evaluated after re-assigning to .a
    };
    
    std.debug.print("{}, {}\n", .{t1.a, t1.b}); // false, false
    
    t2 = .{
        .b = t2.a, // t2.a is evaluated before re-assigning to .a
        .a = false,
    };
    
    std.debug.print("{}, {}\n", .{t2.a, t2.b}); // false, true
}

Welcome to the lovely world of result location semantics Documentation - The Zig Programming Language

the best part is if you dont infer the types .{}T{}, the result changes :smiley:

I can’t find any description of the order of evaluation, but I can deduce from the examples in the link above and your own, that it is the order from the source.

1 Like

Wow, even while it’s documented that’s quite unexpected. Definitely worth the gotcha list for the primer for developers coming from other languages. Especially developers used to swizzles as they’re used to rely on mechanics like this.

I think most developers from other languages would be fooled by this line from the docs:

arr = .{ arr[1], arr[0] };

This statement does NOT swap instead it sets both values to arr [1].

Strict left-to-right. That does mean that the address (result location) is determined before anything is written to it, and if they overlap, unexpected things can indeed happen.

2 Likes