Zig: non-exhaustive enums

Created on 20 May 2019  路  2Comments  路  Source: ziglang/zig

A non-exhaustive enum is an enum that may have other unknown members.
This can help create APIs that may be extended in future. This sort of thing frequently comes up in data formats, network protocols, and even C APIs.

Possible syntax:

  • a non-exhaustive enum is created by including _ as a trailing field.

Semantics:

  • A non-exhaustive enum must have the size specified: enum(u5) { X, Y, Z, _ }
  • If you specify _ when an enum is already "full" (e.g. if it's u2 and you specify 4 values followed by a _), then there is a compiler error.
  • When switch-ed on, an else clause is always required
  • @intToEnum can never fail with has no tag matching integer value
  • builtin.Enum gains a new boolean field is_exhaustive

Related

accepted proposal

Most helpful comment

3909 made all extern enums non-exhaustive. However, I don't think that should necessarily be how it is. Just because an enum is part of an ABI does not mean it is non-exhaustive. According to how the C ABI works, whether an enum is exhaustive or not is part of the documentation of the API.

So #3909 solved a problem, but it also introduced a problem. And I do think that this proposal solves the new problem.

With this proposal accepted, extern enums can be annotated as exhaustive or non-exhaustive. translate-c will choose non-exhaustive to be safe.

One more part of the specification

  • non-exhaustive enums can be used as the tag type of a tagged union. Every tagged union with a non-exhaustive enum as the tag type would be assumed to have void field types for all unnamed tag values.

I also want to include an extra feature of switch to go along with this proposal. The problem is that when an else is required on a switch, the compile error that tells you about missing tag names is gone. However, programmers should still be able to opt-in to compile errors when new tag names are added to non-exhaustive enums. So I will propose that switch gains _ syntax, which does the following:

  • makes it a compile error if all the known tag names are not handled by the switch
  • allows omitting else

Example:

const E = enum {
    a,
    b,
    _,
};

test "non-exhaustive switch" {
    var e: E = .b;
    switch (e) {
        .a => {},
        .b => {},
        _ => {
            // handle unnamed tag values
        },
        // no else required
        // compile error will occur if another tag is added to E
    }

    switch (e) {
        .a => {},
        .b => {},
        else => {}, // OK; no compile error if another tag is added to E
    }

    switch (e) { // error: switch not handling the tag `b`
        .a => {},
        _ => {},
    }

    switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong
        .a => {},
        .b => {},
    }
}

I want to note that there is a case when you would do _ => unreachable. This is when you have a non-exhaustive extern enum because it defines a C ABI, however you know at the switch site that the library will always be linked statically; never dynamically. This means that the value of the enum will never be unnamed, because if the static library was updated, then the zig code would be recompiled against the corresponding updated enum.

Related: #3991

All 2 comments

3909 merged some related functionality as a property of extern enums:

  • Non-exhaustive: else prong always needed in switch.
  • @intToEnum can't fail.

3909 made all extern enums non-exhaustive. However, I don't think that should necessarily be how it is. Just because an enum is part of an ABI does not mean it is non-exhaustive. According to how the C ABI works, whether an enum is exhaustive or not is part of the documentation of the API.

So #3909 solved a problem, but it also introduced a problem. And I do think that this proposal solves the new problem.

With this proposal accepted, extern enums can be annotated as exhaustive or non-exhaustive. translate-c will choose non-exhaustive to be safe.

One more part of the specification

  • non-exhaustive enums can be used as the tag type of a tagged union. Every tagged union with a non-exhaustive enum as the tag type would be assumed to have void field types for all unnamed tag values.

I also want to include an extra feature of switch to go along with this proposal. The problem is that when an else is required on a switch, the compile error that tells you about missing tag names is gone. However, programmers should still be able to opt-in to compile errors when new tag names are added to non-exhaustive enums. So I will propose that switch gains _ syntax, which does the following:

  • makes it a compile error if all the known tag names are not handled by the switch
  • allows omitting else

Example:

const E = enum {
    a,
    b,
    _,
};

test "non-exhaustive switch" {
    var e: E = .b;
    switch (e) {
        .a => {},
        .b => {},
        _ => {
            // handle unnamed tag values
        },
        // no else required
        // compile error will occur if another tag is added to E
    }

    switch (e) {
        .a => {},
        .b => {},
        else => {}, // OK; no compile error if another tag is added to E
    }

    switch (e) { // error: switch not handling the tag `b`
        .a => {},
        _ => {},
    }

    switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong
        .a => {},
        .b => {},
    }
}

I want to note that there is a case when you would do _ => unreachable. This is when you have a non-exhaustive extern enum because it defines a C ABI, however you know at the switch site that the library will always be linked statically; never dynamically. This means that the value of the enum will never be unnamed, because if the static library was updated, then the zig code would be recompiled against the corresponding updated enum.

Related: #3991

Was this page helpful?
0 / 5 - 0 ratings