Zig: when multiple union fields share the same type, allow them to share a body in a switch prong

Created on 13 Jun 2018  路  14Comments  路  Source: ziglang/zig

const Foo = union(enum) {
    One: i32,
    Two: bool,
    Three: i32,
};
fn bar(f: Foo) void {
    switch (f) {
        Foo.One, Foo.Three => |x| { },
        Foo.Two => |b| { },
    }
}
accepted proposal

Most helpful comment

The fact that else gives a payload which is the same thing (and type) as the target expression is intentional, and @emekoi pointed out above where I used it in the standard library. I'm open to the proposal to reverse this decision, but it's very much intentional in status quo.

I do think it should be a compile error on other switch prongs, however, when they do not share the same type.

All 14 comments

It should be possible to omit the Foo prefix. There's no ambiguity.


And if the unioned types are unique, I may prefer to use them:

const Foo = union(enum) {
    One: i32,
    Two: bool,
    Three: u32,
};
fn bar(f: Foo) void {
    switch (f) {
        u32, i32=> |x| { },
        bool => |b| { },
    }
}

It should be possible to omit the Foo prefix. There's no ambiguity.

683

The above behavior works now but there might be a soundness issue here as the following works too:

const Foo = union(enum) {
    One: i32,
    Two: bool,
    Three: i32,
};

fn bar(f: Foo) void {
    switch (f) {
        .One, .Two, .Three => |x| {
            @import("std").debug.warn("{}\n", x);
        },
    }
}

test "" {
    bar(Foo{ .One = 21 });
    bar(Foo{ .Two = false });
    bar(Foo{ .Three = -1 });
}

Hmm yes that looks like a missing compile error.

The above behavior works now but there might be a soundness issue here as the following works too:

It's not unsound, x is not the payload but f, it's just...weird?

Yeah. The type of x becomes Foo when more than one cases are specified.

x is not the payload but f

This has bitten me before. When I see |x|, I always expect it to unwrap something. So in this case I would not expect x to be the same type as f. Not to mention that the behavior of "|x| unwraps, unless the case's tag types don't match" is pretty gnarly.

then would this not be allowed?

fn bar(f: Foo) void {
    switch (f) {
        .One => |x| {
            @import("std").debug.warn("{}\n", x);
        },
        // tag types don't match
        else => |x| {
            @import("std").debug.warn("{}\n", x);
        },
    }
}

then would this not be allowed?

I would say it should not be allowed, because the else => should have the same effective behavior as .Two, .Three =>.

My intuition is that |x| always unwraps, and to get the original Foo you must use f.

would that be a compiler error then?

Yep, a compiler error would make sense. Something along these lines

error: cannot unwrap union when tag types don't match

note: mismatched types were Foo.two (bool), Foo.three (i32)

on second thought i think the existing behavior is correct for else prongs. it allows for less verbose code like this in the standard library:

switch (kernel32.GetLastError()) {
    ERROR.SHARING_VIOLATION => return error.SharingViolation,
    ERROR.ALREADY_EXISTS => return error.PathAlreadyExists,
    ERROR.FILE_EXISTS => return error.PathAlreadyExists,
    ERROR.FILE_NOT_FOUND => return error.FileNotFound,
    ERROR.PATH_NOT_FOUND => return error.FileNotFound,
    ERROR.ACCESS_DENIED => return error.AccessDenied,
    ERROR.PIPE_BUSY => return error.PipeBusy,
    ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
    else => |err| return unexpectedError(err),
}

An alternative, if capturing an else were to be an error:

const err = kernel32.GetLastError(); // store in `e` rather than using `|err|`
switch (err) {
    ERROR.SHARING_VIOLATION => return error.SharingViolation,
    // ...
    else => return unexpectedError(err),
}

It would still be confusing to me if |err| sometimes unwraps and sometimes doesn't, depending on context. Though maybe it's a little better if that inconsistency were isolated only to else conditions.

The fact that else gives a payload which is the same thing (and type) as the target expression is intentional, and @emekoi pointed out above where I used it in the standard library. I'm open to the proposal to reverse this decision, but it's very much intentional in status quo.

I do think it should be a compile error on other switch prongs, however, when they do not share the same type.

Was this page helpful?
0 / 5 - 0 ratings