I accidently write a peice of bad code, but it compiles. I'm comfused on the semantics

This is a simplified demo:

const std = @import("std");

pub fn foo(b: bool, b2: bool) bool {
    if (b) !return b2; // should be: if (b) return !b2;
    return true;
}

pub fn main() !void {    
    std.debug.print("{}\n", .{foo(true, true)});
}

It prints true (false is expected).

Bug or not?

(zig 0.15.2)

1 Like

I think it “simplifies” to !(return b2), which just returns b2 before the negation is evaluated.

Still, return b2 shouldn’t be an expression that could even be negated. So I think this is a bug which should be reported.

2 Likes

This also compiles:

pub fn foo(b: bool, b2: bool) bool {
    if (b) (return b2).xyz;
    return false;
}

Maybe @xjlqr is right. It looks there are something I have not got in Zig.

This looks like a bug, I don’t think there is anything to “get” here. The best thing would be to report it

This is a tricky one.

The expression return smth is of type noreturn because even if it returns from the function, it doesn’t return from the expression.

Now noreturn can coerce into any type because it doesn’t ever actually holds a value. A good example of that is unreachable or @panic whose type is noreturn. You can put unreachable as a “value” into any type because it doesn’t ever actually goes in the location.

So here, return ... coerces into a boolean. You can also boolean or return ... for example.

10 Likes

return a is a primary expression of type noreturn

noreturn coerces to anything with the promise that whatever type is there isn’t actually compared to the other values in the expression, so it doesn’t matter what it is. That’s what allows it to slip in basically anywhere to return error values from try.

Maybe it should be some sort of compile diagnostics to let you know this is legal but probably wrong, but I don’t think you can remove it entirely without breaking a lot of things.

4 Likes

We’re a minute apart from the same comment :joy:

I think it might be worth considering forbidding noreturn to coerce because of an operator that doesn’t affect control flow. Otherwise it’s a very useful feature.

Yea, I had a good chuckle too. Had already written the whole comment when you posted yours, figured I may as well put it out there. :sweat_smile:

I’m pretty sure this is already the case for binary operators (==, +, etc).

a == return a is a compile error with control flow diverted, which makes sense to me.

I think try is the only prefix operator that affects control flow, so maybe it’s just a missed edge case.

It works with orelse, catch, or and and, too.

But you’re right it works with all unary operators, which it should only with try.

You can do with return whatever your heart desire:

the noreturn type is compatible with every other type

edit: i should have read the rest of comments before typing

This gives rise to a poetic alternative to if (true) return;

return or noreturn;

11 Likes

_ = true/false or return;

It is not the whole story. boolean or return ... is only valid when boolean is a compile-time known false. And boolean and return ... is only valid when boolean is a compile-time known true. Otherwise, they must be assigned to a value.

It looks, when boolean is compile-time known, boolean or return ... and boolean and return ... are just translated to return ... at a phase during compiling.

Similarly, if (b) !return b2; is also translated to if (b) return b2; at a phase. If the translation is not made, then the line must be written as _ = if (b) !return b2 else false; etc.

1 Like

no, it compiles and runs with true or false, both comptime and runtime known zig 0.15.2 my example of _ = b or return does at least

Yes, as I said, it b is not compile-time know, the expression must be assigned to a value.

1 Like

No, I wrote an expression, not a statement :wink:

I mean why if (b) !return b2; compiles is not only because noreturn values can coerce into any type, but also because it is translated to `if (b) return b2 at a phase. If the translation is not made, then the line must be written as _ = if (b) !return b2 else false; etc.

In fact, I still have some confusions:

pub fn bar() bool {
    !return true; // okay
    &return true; // okay
    ~return true; // okay
    -return true; // okay
    +return true; // error
}