Skip to content

Commit cf69154

Browse files
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 <[email protected]>
1 parent e17dfb9 commit cf69154

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

doc/langref.html.in

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2495,6 +2495,53 @@ or
24952495
</p>
24962496
{#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+
24982545
{#header_close#}
24992546

25002547
{#header_open|Inline Switch Prongs#}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const std = @import("std");
2+
3+
test "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
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const std = @import("std");
2+
3+
test "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
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const std = @import("std");
2+
const expectEqual = std.testing.expectEqual;
3+
4+
const Instruction = enum {
5+
add,
6+
mul,
7+
end,
8+
};
9+
10+
fn 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+
33+
test "evaluate" {
34+
const result = try evaluate(&.{ 7, 2, -3 }, &.{ .mul, .add, .end });
35+
try expectEqual(1, result);
36+
}
37+
38+
// test

0 commit comments

Comments
 (0)