mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
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>
This commit is contained in:
parent
e17dfb9da0
commit
cf69154332
@ -2495,6 +2495,53 @@ or
|
|||||||
</p>
|
</p>
|
||||||
{#code|test_exhaustive_switch.zig#}
|
{#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_close#}
|
||||||
|
|
||||||
{#header_open|Inline Switch Prongs#}
|
{#header_open|Inline Switch Prongs#}
|
||||||
|
|||||||
26
doc/langref/test_switch_continue.zig
Normal file
26
doc/langref/test_switch_continue.zig
Normal file
@ -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
|
||||||
28
doc/langref/test_switch_continue_equivalent.zig
Normal file
28
doc/langref/test_switch_continue_equivalent.zig
Normal file
@ -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
|
||||||
38
doc/langref/test_switch_dispatch_loop.zig
Normal file
38
doc/langref/test_switch_dispatch_loop.zig
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user