Commit cf69154332

LiterallyVoid <literallyvoid@gmail.com>
2024-09-13 05:06:49
Labeled switch documentation (#21383)
Add langref docs for labeled switch This feature was proposed in #8220, and implemented in #21257. Co-authored-by: Andrew Kelley <andrew@ziglang.org>
1 parent e17dfb9
doc/langref/test_switch_continue.zig
@@ -0,0 +1,26 @@
+const std = @import("std");
+
+test "switch continue" {
+    sw: switch (@as(i32, 5)) {
+        5 => continue :sw 4,
+
+        // `continue` can occur multiple times within a single switch prong.
+        2...4 => |v| {
+            if (v > 3) {
+                continue :sw 2;
+            } else if (v == 3) {
+
+                // `break` can target labeled loops.
+                break :sw;
+            }
+
+            continue :sw 1;
+        },
+
+        1 => return,
+
+        else => unreachable,
+    }
+}
+
+// test
doc/langref/test_switch_continue_equivalent.zig
@@ -0,0 +1,28 @@
+const std = @import("std");
+
+test "switch continue, equivalent loop" {
+    var sw: i32 = 5;
+    while (true) {
+        switch (sw) {
+            5 => {
+                sw = 4;
+                continue;
+            },
+            2...4 => |v| {
+                if (v > 3) {
+                    sw = 2;
+                    continue;
+                } else if (v == 3) {
+                    break;
+                }
+
+                sw = 1;
+                continue;
+            },
+            1 => return,
+            else => unreachable,
+        }
+    }
+}
+
+// test
doc/langref/test_switch_dispatch_loop.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+const expectEqual = std.testing.expectEqual;
+
+const Instruction = enum {
+    add,
+    mul,
+    end,
+};
+
+fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
+    var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
+    var ip: usize = 0;
+
+    return vm: switch (code[ip]) {
+        // Because all code after `continue` is unreachable, this branch does
+        // not provide a result.
+        .add => {
+            try stack.append(stack.pop() + stack.pop());
+
+            ip += 1;
+            continue :vm code[ip];
+        },
+        .mul => {
+            try stack.append(stack.pop() * stack.pop());
+
+            ip += 1;
+            continue :vm code[ip];
+        },
+        .end => stack.pop(),
+    };
+}
+
+test "evaluate" {
+    const result = try evaluate(&.{ 7, 2, -3 }, &.{ .mul, .add, .end });
+    try expectEqual(1, result);
+}
+
+// test
doc/langref.html.in
@@ -2495,6 +2495,53 @@ or
       </p>
       {#code|test_exhaustive_switch.zig#}
 
+      {#header_close#}
+
+      {#header_open|Labeled switch#}
+      <p>
+      When a switch statement is labeled, it can be referenced from a
+      {#syntax#}break{#endsyntax#} or {#syntax#}continue{#endsyntax#}.
+      {#syntax#}break{#endsyntax#} will return a value from the {#syntax#}
+      switch{#endsyntax#}.
+      </p>
+      <p>
+      A {#syntax#}continue{#endsyntax#} targeting a switch must have an
+      operand. When executed, it will jump to the matching prong, as if the
+      {#syntax#}switch{#endsyntax#} were executed again with the {#syntax#}
+      continue{#endsyntax#}'s operand replacing the initial switch value.
+      </p>
+
+      {#code|test_switch_continue.zig#}
+
+      <p>
+      Semantically, this is equivalent to the following loop:
+      </p>
+      {#code|test_switch_continue_equivalent.zig#}
+
+      <p>
+      This can improve clarity of (for example) state machines, where the syntax {#syntax#}continue :sw .next_state{#endsyntax#} is unambiguous, explicit, and immediately understandable.
+      </p>
+      <p>
+      However, the motivating example is a switch on each element of an array, where using a single switch can improve clarity and performance:
+      </p>
+      {#code|test_switch_dispatch_loop.zig#}
+
+      <p>
+      If the operand to {#syntax#}continue{#endsyntax#} is
+      {#link|comptime#}-known, then it can be lowered to an unconditional branch
+      to the relevant case. Such a branch is perfectly predicted, and hence
+      typically very fast to execute.
+      </p>
+
+      <p>
+      If the operand is runtime-known, each {#syntax#}continue{#endsyntax#} can
+      embed a conditional branch inline (ideally through a jump table), which
+      allows a CPU to predict its target independently of any other prong. A
+      loop-based lowering would force every branch through the same dispatch
+      point, hindering branch prediction.
+      </p>
+
+
       {#header_close#}
 
       {#header_open|Inline Switch Prongs#}