Zig: some expressions should be statements instead

Created on 19 Nov 2018  路  6Comments  路  Source: ziglang/zig

It's strange that this is possible:

const std = @import("std");

test "aoeu" {
    var void_value = @setCold(false);
    std.debug.assert(void_value == {});
    void_value = @setRuntimeSafety(true);
}

This should be error: @setCold is a statement not an expression or something like that.

Also, this is more of an implementation detail, but the reason you can't do...

test "aoeu" {
    var void_value = defer {};
}

...is only because of parsing rules. The implementation of Zig IR should probably recognize the difference between statements and expressions.

breaking proposal

Most helpful comment

While the examples look weird, they do no harm, and disabling void expressions will do harm, at least to generics.

Disallowiing void expressions will likely make Zig deal with complications in generic code. Also it will be weird to be able to assign result from ErrorSet!void but not from pure void. In my opinion void expressions are part of the language elegance.

Zig has so far avoided the initial mistake of C when they introduced void as a special restricted type. C++ was forced to relax the void restrictions in order to simplify generics, so, for example, one can use a return statement in a tail-call to possibly-void function in generic context. Thats a mouthful. I'd rather be able to do it anytime, it does no harm if the result is void.

All 6 comments

What is the difference between a statement and an expression that returns a void? Would this mean we can't have variables of type void?

There's no difference currently. But the difference I am proposing is that it is a statement does not return anything; an attempt to use its nonexistent return value is a compile error that says "statement used as expression".

I know this is silly, but how would something like this work:

fn foo(comptime a: bool, value: u8) (if(a) u8 else void) {
   return if (a) value else {};
}

test "void" {
    var x = foo(true, 1);
    var y = foo(false, 1);
}

Would we have to call foo in different ways depending on the first parameter?

I propose to make the following constructs purely statements:

// Never value producing
resume Expr;
cancel Expr;
// Value producing only in an enclosing scope - not within an AssignExpr or similar
break BreakLabel? Expr?;
continue BreakLabel?;
return Expr?;

These are not value producing and will probably result in a compile error if used as expressions.

EDIT: After some experimentation I concluded that this cannot be accomplished in a nice way. The reason lies in the orelse and catch allowing a noreturn rhs and e.g. IfExpr needs access to the flow controls.

// Never value producing
resume Expr;

I've been considering if it makes sense for coroutines to yield values (which would be returned via resume): this is a simple basis for generators.
However those thoughts are pending the coroutine rewrite

While the examples look weird, they do no harm, and disabling void expressions will do harm, at least to generics.

Disallowiing void expressions will likely make Zig deal with complications in generic code. Also it will be weird to be able to assign result from ErrorSet!void but not from pure void. In my opinion void expressions are part of the language elegance.

Zig has so far avoided the initial mistake of C when they introduced void as a special restricted type. C++ was forced to relax the void restrictions in order to simplify generics, so, for example, one can use a return statement in a tail-call to possibly-void function in generic context. Thats a mouthful. I'd rather be able to do it anytime, it does no harm if the result is void.

Was this page helpful?
0 / 5 - 0 ratings