authorgravatar for literallyvoid@gmail.comLiterallyVoid <literallyvoid@gmail.com> 2024-09-12 20:06:49-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-09-12 20:06:49-07:00
logcf691543323be9bb663aac2d19f62412435a4d39
treedb7cc5a430779ba47b048da5f94320dfd1210a1e
parente17dfb9da0bee4c1f118e0a72b88f29f43365f61
signaturebadge-check Signed by PGP key B5690EEEBB952194

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>

4 files changed, 139 insertions(+), 0 deletions(-)

doc/langref.html.in+47
...@@ -2495,6 +2495,53 @@ or...@@ -2495,6 +2495,53 @@ or
2495 </p>2495 </p>
2496 {#code|test_exhaustive_switch.zig#}2496 {#code|test_exhaustive_switch.zig#}
24972497
2498 {#header_close#}
2499
2500 {#header_open|Labeled switch#}
2501 <p>
2502 When a switch statement is labeled, it can be referenced from a
2503 {#syntax#}break{#endsyntax#} or {#syntax#}continue{#endsyntax#}.
2504 {#syntax#}break{#endsyntax#} will return a value from the {#syntax#}
2505 switch{#endsyntax#}.
2506 </p>
2507 <p>
2508 A {#syntax#}continue{#endsyntax#} targeting a switch must have an
2509 operand. When executed, it will jump to the matching prong, as if the
2510 {#syntax#}switch{#endsyntax#} were executed again with the {#syntax#}
2511 continue{#endsyntax#}'s operand replacing the initial switch value.
2512 </p>
2513
2514 {#code|test_switch_continue.zig#}
2515
2516 <p>
2517 Semantically, this is equivalent to the following loop:
2518 </p>
2519 {#code|test_switch_continue_equivalent.zig#}
2520
2521 <p>
2522 This can improve clarity of (for example) state machines, where the syntax {#syntax#}continue :sw .next_state{#endsyntax#} is unambiguous, explicit, and immediately understandable.
2523 </p>
2524 <p>
2525 However, the motivating example is a switch on each element of an array, where using a single switch can improve clarity and performance:
2526 </p>
2527 {#code|test_switch_dispatch_loop.zig#}
2528
2529 <p>
2530 If the operand to {#syntax#}continue{#endsyntax#} is
2531 {#link|comptime#}-known, then it can be lowered to an unconditional branch
2532 to the relevant case. Such a branch is perfectly predicted, and hence
2533 typically very fast to execute.
2534 </p>
2535
2536 <p>
2537 If the operand is runtime-known, each {#syntax#}continue{#endsyntax#} can
2538 embed a conditional branch inline (ideally through a jump table), which
2539 allows a CPU to predict its target independently of any other prong. A
2540 loop-based lowering would force every branch through the same dispatch
2541 point, hindering branch prediction.
2542 </p>
2543
2544
2498 {#header_close#}2545 {#header_close#}
24992546
2500 {#header_open|Inline Switch Prongs#}2547 {#header_open|Inline Switch Prongs#}
doc/langref/test_switch_continue.zig created+26
...@@ -0,0 +1,26 @@
1const std = @import("std");
2
3test "switch continue" {
4 sw: switch (@as(i32, 5)) {
5 5 => continue :sw 4,
6
7 // `continue` can occur multiple times within a single switch prong.
8 2...4 => |v| {
9 if (v > 3) {
10 continue :sw 2;
11 } else if (v == 3) {
12
13 // `break` can target labeled loops.
14 break :sw;
15 }
16
17 continue :sw 1;
18 },
19
20 1 => return,
21
22 else => unreachable,
23 }
24}
25
26// test
doc/langref/test_switch_continue_equivalent.zig created+28
...@@ -0,0 +1,28 @@
1const std = @import("std");
2
3test "switch continue, equivalent loop" {
4 var sw: i32 = 5;
5 while (true) {
6 switch (sw) {
7 5 => {
8 sw = 4;
9 continue;
10 },
11 2...4 => |v| {
12 if (v > 3) {
13 sw = 2;
14 continue;
15 } else if (v == 3) {
16 break;
17 }
18
19 sw = 1;
20 continue;
21 },
22 1 => return,
23 else => unreachable,
24 }
25 }
26}
27
28// test
doc/langref/test_switch_dispatch_loop.zig created+38
...@@ -0,0 +1,38 @@
1const std = @import("std");
2const expectEqual = std.testing.expectEqual;
3
4const Instruction = enum {
5 add,
6 mul,
7 end,
8};
9
10fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
11 var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
12 var ip: usize = 0;
13
14 return vm: switch (code[ip]) {
15 // Because all code after `continue` is unreachable, this branch does
16 // not provide a result.
17 .add => {
18 try stack.append(stack.pop() + stack.pop());
19
20 ip += 1;
21 continue :vm code[ip];
22 },
23 .mul => {
24 try stack.append(stack.pop() * stack.pop());
25
26 ip += 1;
27 continue :vm code[ip];
28 },
29 .end => stack.pop(),
30 };
31}
32
33test "evaluate" {
34 const result = try evaluate(&.{ 7, 2, -3 }, &.{ .mul, .add, .end });
35 try expectEqual(1, result);
36}
37
38// test