| author | |
| committer | |
| log | cf691543323be9bb663aac2d19f62412435a4d39 |
| tree | db7cc5a430779ba47b048da5f94320dfd1210a1e |
| parent | e17dfb9da0bee4c1f118e0a72b88f29f43365f61 |
| signature |
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 | 2495 | </p> |
| 2496 | 2496 | {#code|test_exhaustive_switch.zig#} |
| 2497 | 2497 | |
| 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 | 2545 | {#header_close#} |
| 2499 | 2546 | |
| 2500 | 2547 | {#header_open|Inline Switch Prongs#} |
doc/langref/test_switch_continue.zig created+26| ... | ... | @@ -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 |
doc/langref/test_switch_continue_equivalent.zig created+28| ... | ... | @@ -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 |
doc/langref/test_switch_dispatch_loop.zig created+38| ... | ... | @@ -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 |