switch
test_switch.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
test "switch simple" {
const a: u64 = 10;
const zz: u64 = 103;
// All branches of a switch expression must be able to be coerced to a
// common type.
//
// Branches cannot fallthrough. If fallthrough behavior is desired, combine
// the cases and use an if.
const b = switch (a) {
// Multiple cases can be combined via a ','
1, 2, 3 => 0,
// Ranges can be specified using the ... syntax. These are inclusive
// of both ends.
5...100 => 1,
// Branches can be arbitrarily complex.
101 => blk: {
const c: u64 = 5;
break :blk c * 2 + 1;
},
// Switching on arbitrary expressions is allowed as long as the
// expression is known at compile-time.
zz => zz,
blk: {
const d: u32 = 5;
const e: u32 = 100;
break :blk d + e;
} => 107,
// The else branch catches everything not already captured.
// Else branches are mandatory unless the entire range of values
// is handled.
else => 9,
};
try expect(b == 1);
}
// Switch expressions can be used outside a function:
const os_msg = switch (builtin.target.os.tag) {
.linux => "we found a linux user",
else => "not a linux user",
};
// Inside a function, switch statements implicitly are compile-time
// evaluated if the target expression is compile-time known.
test "switch inside function" {
switch (builtin.target.os.tag) {
.fuchsia => {
// On an OS other than fuchsia, block is not even analyzed,
// so this compile error is not triggered.
// On fuchsia this compile error would be triggered.
@compileError("fuchsia not supported");
},
else => {},
}
}
Shell
$ zig test test_switch.zig
1/2 test.switch simple... OK
2/2 test.switch inside function... OK
All 2 tests passed.
switch
can be used to capture the field values of a Tagged union. Modifications to the field values can be done by placing a *
before the capture variable name, turning it into a pointer.
test_switch_tagged_union.zig
const expect = @import("std").testing.expect;
test "switch on tagged union" {
const Point = struct {
x: u8,
y: u8,
};
const Item = union(enum) {
a: u32,
c: Point,
d,
e: u32,
};
var a = Item{ .c = Point{ .x = 1, .y = 2 } };
// Switching on more complex enums is allowed.
const b = switch (a) {
// A capture group is allowed on a match, and will return the enum
// value matched. If the payload types of both cases are the same
// they can be put into the same switch prong.
Item.a, Item.e => |item| item,
// A reference to the matched value can be obtained using `*` syntax.
Item.c => |*item| blk: {
item.*.x += 1;
break :blk 6;
},
// No else is required if the types cases was exhaustively handled
Item.d => 8,
};
try expect(b == 6);
try expect(a.c.x == 2);
}
Shell
$ zig test test_switch_tagged_union.zig
1/1 test.switch on tagged union... OK
All 1 tests passed.
See also:
Exhaustive Switching
When a switch
expression does not have an else
clause, it must exhaustively list all the possible values. Failure to do so is a compile error:
test_unhandled_enumeration_value.zig
const Color = enum {
auto,
off,
on,
};
test "exhaustive switching" {
const color = Color.off;
switch (color) {
Color.auto => {},
Color.on => {},
}
}
Shell
$ zig test test_unhandled_enumeration_value.zig
docgen_tmp/test_unhandled_enumeration_value.zig:9:5: error: switch must handle all possibilities
switch (color) {
^~~~~~
docgen_tmp/test_unhandled_enumeration_value.zig:3:5: note: unhandled enumeration value: 'off'
off,
^~~
docgen_tmp/test_unhandled_enumeration_value.zig:1:15: note: enum 'test_unhandled_enumeration_value.Color' declared here
const Color = enum {
^~~~
Switching with Enum Literals
Enum Literals can be useful to use with switch
to avoid repetitively specifying enum or union types:
test_exhaustive_switch.zig
const std = @import("std");
const expect = std.testing.expect;
const Color = enum {
auto,
off,
on,
};
test "enum literals with switch" {
const color = Color.off;
const result = switch (color) {
.auto => false,
.on => false,
.off => true,
};
try expect(result);
}
Shell
$ zig test test_exhaustive_switch.zig
1/1 test.enum literals with switch... OK
All 1 tests passed.
Inline switch
Switch prongs can be marked as inline
to generate the prong’s body for each possible value it could have:
test_inline_switch.zig
const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;
fn isFieldOptional(comptime T: type, field_index: usize) !bool {
const fields = @typeInfo(T).Struct.fields;
return switch (field_index) {
// This prong is analyzed `fields.len - 1` times with `idx` being a
// unique comptime-known value each time.
inline 0...fields.len - 1 => |idx| @typeInfo(fields[idx].type) == .Optional,
else => return error.IndexOutOfBounds,
};
}
const Struct1 = struct { a: u32, b: ?u32 };
test "using @typeInfo with runtime values" {
var index: usize = 0;
try expect(!try isFieldOptional(Struct1, index));
index += 1;
try expect(try isFieldOptional(Struct1, index));
index += 1;
try expectError(error.IndexOutOfBounds, isFieldOptional(Struct1, index));
}
// Calls to `isFieldOptional` on `Struct1` get unrolled to an equivalent
// of this function:
fn isFieldOptionalUnrolled(field_index: usize) !bool {
return switch (field_index) {
0 => false,
1 => true,
else => return error.IndexOutOfBounds,
};
}
Shell
$ zig test test_inline_switch.zig
1/1 test.using @typeInfo with runtime values... OK
All 1 tests passed.
inline else
prongs can be used as a type safe alternative to inline for
loops:
test_inline_else.zig
const std = @import("std");
const expect = std.testing.expect;
const SliceTypeA = extern struct {
len: usize,
ptr: [*]u32,
};
const SliceTypeB = extern struct {
ptr: [*]SliceTypeA,
len: usize,
};
const AnySlice = union(enum) {
a: SliceTypeA,
b: SliceTypeB,
c: []const u8,
d: []AnySlice,
};
fn withFor(any: AnySlice) usize {
const Tag = @typeInfo(AnySlice).Union.tag_type.?;
inline for (@typeInfo(Tag).Enum.fields) |field| {
// With `inline for` the function gets generated as
// a series of `if` statements relying on the optimizer
// to convert it to a switch.
if (field.value == @intFromEnum(any)) {
return @field(any, field.name).len;
}
}
// When using `inline for` the compiler doesn't know that every
// possible case has been handled requiring an explicit `unreachable`.
unreachable;
}
fn withSwitch(any: AnySlice) usize {
return switch (any) {
// With `inline else` the function is explicitly generated
// as the desired switch and the compiler can check that
// every possible case is handled.
inline else => |slice| slice.len,
};
}
test "inline for and inline else similarity" {
var any = AnySlice{ .c = "hello" };
try expect(withFor(any) == 5);
try expect(withSwitch(any) == 5);
}
Shell
$ zig test test_inline_else.zig
1/1 test.inline for and inline else similarity... OK
All 1 tests passed.
When using an inline prong switching on an union an additional capture can be used to obtain the union’s enum tag value.
test_inline_switch_union_tag.zig
const std = @import("std");
const expect = std.testing.expect;
const U = union(enum) {
a: u32,
b: f32,
};
fn getNum(u: U) u32 {
switch (u) {
// Here `num` is a runtime-known value that is either
// `u.a` or `u.b` and `tag` is `u`'s comptime-known tag value.
inline else => |num, tag| {
if (tag == .b) {
return @intFromFloat(num);
}
return num;
}
}
}
test "test" {
var u = U{ .b = 42 };
try expect(getNum(u) == 42);
}
Shell
$ zig test test_inline_switch_union_tag.zig
1/1 test.test... OK
All 1 tests passed.
See also: