| 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,6 +2495,53 @@ or |
| 2495 | </p> | 2495 | </p> |
| 2496 | {#code|test_exhaustive_switch.zig#} | 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 | {#header_close#} | 2545 | {#header_close#} |
| 2499 | 2546 | ||
| 2500 | {#header_open|Inline Switch Prongs#} | 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 | ||