Skip to content

Commit

Permalink
Labeled switch documentation (#21383)
Browse files Browse the repository at this point in the history
Add langref docs for labeled switch

This feature was proposed in #8220, and implemented in #21257.

Co-authored-by: Andrew Kelley <andrew@ziglang.org>
  • Loading branch information
LiterallyVoid and andrewrk authored Sep 13, 2024
1 parent e17dfb9 commit cf69154
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
47 changes: 47 additions & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -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#}
Expand Down
26 changes: 26 additions & 0 deletions doc/langref/test_switch_continue.zig
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions doc/langref/test_switch_continue_equivalent.zig
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions doc/langref/test_switch_dispatch_loop.zig
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit cf69154

Please sign in to comment.