switch

test_switch.zig

  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const expect = std.testing.expect;
  4. test "switch simple" {
  5. const a: u64 = 10;
  6. const zz: u64 = 103;
  7. // All branches of a switch expression must be able to be coerced to a
  8. // common type.
  9. //
  10. // Branches cannot fallthrough. If fallthrough behavior is desired, combine
  11. // the cases and use an if.
  12. const b = switch (a) {
  13. // Multiple cases can be combined via a ','
  14. 1, 2, 3 => 0,
  15. // Ranges can be specified using the ... syntax. These are inclusive
  16. // of both ends.
  17. 5...100 => 1,
  18. // Branches can be arbitrarily complex.
  19. 101 => blk: {
  20. const c: u64 = 5;
  21. break :blk c * 2 + 1;
  22. },
  23. // Switching on arbitrary expressions is allowed as long as the
  24. // expression is known at compile-time.
  25. zz => zz,
  26. blk: {
  27. const d: u32 = 5;
  28. const e: u32 = 100;
  29. break :blk d + e;
  30. } => 107,
  31. // The else branch catches everything not already captured.
  32. // Else branches are mandatory unless the entire range of values
  33. // is handled.
  34. else => 9,
  35. };
  36. try expect(b == 1);
  37. }
  38. // Switch expressions can be used outside a function:
  39. const os_msg = switch (builtin.target.os.tag) {
  40. .linux => "we found a linux user",
  41. else => "not a linux user",
  42. };
  43. // Inside a function, switch statements implicitly are compile-time
  44. // evaluated if the target expression is compile-time known.
  45. test "switch inside function" {
  46. switch (builtin.target.os.tag) {
  47. .fuchsia => {
  48. // On an OS other than fuchsia, block is not even analyzed,
  49. // so this compile error is not triggered.
  50. // On fuchsia this compile error would be triggered.
  51. @compileError("fuchsia not supported");
  52. },
  53. else => {},
  54. }
  55. }

Shell

  1. $ zig test test_switch.zig
  2. 1/2 test_switch.test.switch simple... OK
  3. 2/2 test_switch.test.switch inside function... OK
  4. 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

  1. const expect = @import("std").testing.expect;
  2. test "switch on tagged union" {
  3. const Point = struct {
  4. x: u8,
  5. y: u8,
  6. };
  7. const Item = union(enum) {
  8. a: u32,
  9. c: Point,
  10. d,
  11. e: u32,
  12. };
  13. var a = Item{ .c = Point{ .x = 1, .y = 2 } };
  14. // Switching on more complex enums is allowed.
  15. const b = switch (a) {
  16. // A capture group is allowed on a match, and will return the enum
  17. // value matched. If the payload types of both cases are the same
  18. // they can be put into the same switch prong.
  19. Item.a, Item.e => |item| item,
  20. // A reference to the matched value can be obtained using `*` syntax.
  21. Item.c => |*item| blk: {
  22. item.*.x += 1;
  23. break :blk 6;
  24. },
  25. // No else is required if the types cases was exhaustively handled
  26. Item.d => 8,
  27. };
  28. try expect(b == 6);
  29. try expect(a.c.x == 2);
  30. }

Shell

  1. $ zig test test_switch_tagged_union.zig
  2. 1/1 test_switch_tagged_union.test.switch on tagged union... OK
  3. 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

  1. const Color = enum {
  2. auto,
  3. off,
  4. on,
  5. };
  6. test "exhaustive switching" {
  7. const color = Color.off;
  8. switch (color) {
  9. Color.auto => {},
  10. Color.on => {},
  11. }
  12. }

Shell

  1. $ zig test test_unhandled_enumeration_value.zig
  2. docgen_tmp/test_unhandled_enumeration_value.zig:9:5: error: switch must handle all possibilities
  3. switch (color) {
  4. ^~~~~~
  5. docgen_tmp/test_unhandled_enumeration_value.zig:3:5: note: unhandled enumeration value: 'off'
  6. off,
  7. ^~~
  8. docgen_tmp/test_unhandled_enumeration_value.zig:1:15: note: enum 'test_unhandled_enumeration_value.Color' declared here
  9. const Color = enum {
  10. ^~~~

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

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const Color = enum {
  4. auto,
  5. off,
  6. on,
  7. };
  8. test "enum literals with switch" {
  9. const color = Color.off;
  10. const result = switch (color) {
  11. .auto => false,
  12. .on => false,
  13. .off => true,
  14. };
  15. try expect(result);
  16. }

Shell

  1. $ zig test test_exhaustive_switch.zig
  2. 1/1 test_exhaustive_switch.test.enum literals with switch... OK
  3. All 1 tests passed.

Inline Switch Prongs

Switch prongs can be marked as inline to generate the prong’s body for each possible value it could have, making the captured value comptime.

test_inline_switch.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const expectError = std.testing.expectError;
  4. fn isFieldOptional(comptime T: type, field_index: usize) !bool {
  5. const fields = @typeInfo(T).Struct.fields;
  6. return switch (field_index) {
  7. // This prong is analyzed twice with `idx` being a
  8. // comptime-known value each time.
  9. inline 0, 1 => |idx| @typeInfo(fields[idx].type) == .Optional,
  10. else => return error.IndexOutOfBounds,
  11. };
  12. }
  13. const Struct1 = struct { a: u32, b: ?u32 };
  14. test "using @typeInfo with runtime values" {
  15. var index: usize = 0;
  16. try expect(!try isFieldOptional(Struct1, index));
  17. index += 1;
  18. try expect(try isFieldOptional(Struct1, index));
  19. index += 1;
  20. try expectError(error.IndexOutOfBounds, isFieldOptional(Struct1, index));
  21. }
  22. // Calls to `isFieldOptional` on `Struct1` get unrolled to an equivalent
  23. // of this function:
  24. fn isFieldOptionalUnrolled(field_index: usize) !bool {
  25. return switch (field_index) {
  26. 0 => false,
  27. 1 => true,
  28. else => return error.IndexOutOfBounds,
  29. };
  30. }

Shell

  1. $ zig test test_inline_switch.zig
  2. 1/1 test_inline_switch.test.using @typeInfo with runtime values... OK
  3. All 1 tests passed.

The inline keyword may also be combined with ranges:

inline_prong_range.zig

  1. fn isFieldOptional(comptime T: type, field_index: usize) !bool {
  2. const fields = @typeInfo(T).Struct.fields;
  3. return switch (field_index) {
  4. inline 0...fields.len - 1 => |idx| @typeInfo(fields[idx].type) == .Optional,
  5. else => return error.IndexOutOfBounds,
  6. };
  7. }

inline else prongs can be used as a type safe alternative to inline for loops:

test_inline_else.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const SliceTypeA = extern struct {
  4. len: usize,
  5. ptr: [*]u32,
  6. };
  7. const SliceTypeB = extern struct {
  8. ptr: [*]SliceTypeA,
  9. len: usize,
  10. };
  11. const AnySlice = union(enum) {
  12. a: SliceTypeA,
  13. b: SliceTypeB,
  14. c: []const u8,
  15. d: []AnySlice,
  16. };
  17. fn withFor(any: AnySlice) usize {
  18. const Tag = @typeInfo(AnySlice).Union.tag_type.?;
  19. inline for (@typeInfo(Tag).Enum.fields) |field| {
  20. // With `inline for` the function gets generated as
  21. // a series of `if` statements relying on the optimizer
  22. // to convert it to a switch.
  23. if (field.value == @intFromEnum(any)) {
  24. return @field(any, field.name).len;
  25. }
  26. }
  27. // When using `inline for` the compiler doesn't know that every
  28. // possible case has been handled requiring an explicit `unreachable`.
  29. unreachable;
  30. }
  31. fn withSwitch(any: AnySlice) usize {
  32. return switch (any) {
  33. // With `inline else` the function is explicitly generated
  34. // as the desired switch and the compiler can check that
  35. // every possible case is handled.
  36. inline else => |slice| slice.len,
  37. };
  38. }
  39. test "inline for and inline else similarity" {
  40. const any = AnySlice{ .c = "hello" };
  41. try expect(withFor(any) == 5);
  42. try expect(withSwitch(any) == 5);
  43. }

Shell

  1. $ zig test test_inline_else.zig
  2. 1/1 test_inline_else.test.inline for and inline else similarity... OK
  3. 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

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const U = union(enum) {
  4. a: u32,
  5. b: f32,
  6. };
  7. fn getNum(u: U) u32 {
  8. switch (u) {
  9. // Here `num` is a runtime-known value that is either
  10. // `u.a` or `u.b` and `tag` is `u`'s comptime-known tag value.
  11. inline else => |num, tag| {
  12. if (tag == .b) {
  13. return @intFromFloat(num);
  14. }
  15. return num;
  16. }
  17. }
  18. }
  19. test "test" {
  20. const u = U{ .b = 42 };
  21. try expect(getNum(u) == 42);
  22. }

Shell

  1. $ zig test test_inline_switch_union_tag.zig
  2. 1/1 test_inline_switch_union_tag.test.test... OK
  3. All 1 tests passed.

See also: