Skip to content

Commit

Permalink
Add information about labeled control flow (#79)
Browse files Browse the repository at this point in the history
* Add information about labeled control flow

* Clean up some examples based on feedback and actually testing them
  • Loading branch information
cgswords authored Jul 19, 2024
1 parent da2f47f commit 05df173
Showing 1 changed file with 94 additions and 11 deletions.
105 changes: 94 additions & 11 deletions reference/src/control-flow/labeled-control-flow.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Labeled Control Flow

Move supports labeled control flow, allowing you to define and transfer control to specific labels
in a function. For example, we can nest two loops and use `break` and `continue` with those labels
to precisely specify control flow. You can prefix any `loop` or `while` form with a `'label:` form
to allow breaking or continuing directly there.
Move supports labeled control flow when writing both loops and blocks of code, allowing you
to `break` and `continue` loops and `return` from blocks (which can be particularly helpful in the
presence of macros).

## Loops

Loops allow you to define and transfer control to specific labels in a function. For example, we can
nest two loops and use `break` and `continue` with those labels to precisely specify control flow.
You can prefix any `loop` or `while` form with a `'label:` form to allow breaking or continuing
directly there.

To demonstrate this behavior, consider a function that takes nested vectors of numbers (i.e.,
`vector<vector<u64>>`) to sum against some threshold, which behaves as follows:
Expand All @@ -19,18 +25,18 @@ outer label to escape both loops at once:
fun sum_until_threshold(input: &vector<vector<u64>>, threshold: u64): u64 {
let mut sum = 0;
let mut i = 0;
let input_size = vector::length(vec);
let input_size = input.length();
'outer: loop {
// breaks to outer since it is the closest enclosing loop
if (i >= input_size) break sum;
let vec = vector::borrow(input, i);
let size = vector::length(vec);
let vec = &input[i];
let size = vec.length();
let mut j = 0;
while (j < size) {
let v_entry = *vector::borrow(vec, j);
let v_entry = vec[j];
if (sum + v_entry < threshold) {
sum = sum + v_entry;
} else {
Expand All @@ -50,16 +56,93 @@ bodies of code. For example, if we were processing a large table where each entr
that might see us continuing the inner or outer loop, we could express that code using labels:

```move
'outer: loop {
let x = 'outer: loop {
...
'inner: while (cond) {
...
if (cond0) { break 'outer value };
...
if (cond1) { continue 'inner }
else if (cond2) { continue 'outer };
else if (cond2) { continue 'outer }
...
}
...
...
};
```

## Labeled Blocks

Labeled blocks allow you to write Move programs that contain intra-function non-local control flow,
including inside of macro lambdas and returning values:

```move
fun named_return(n: u64): vector<u8> {
let x = 'a: {
if (n % 2 == 0) {
return 'a b"even"
};
b"odd"
};
x
}
```

In this simple example, the program checks if the input `n` is even. If it is, the program leaves
the block labeled `'a:` with the value `b"even"`. If not, the code continues, ending the block
labeled `'a:` with the value `b"odd"`. At the end, we set `x` to the value and then return it.

This control flow feature works across macro bodies as well. For example, suppose we wanted to write
a function to find the first even number in a vector, and that we have some macro `for_ref` that
iterates the vector elements in a loop:

```move
macro fun for_ref<$T>($vs: &vector<$T>, $f: |&$T|) {
let vs = $vs;
let mut i = 0;
let end = vs.length();
while (i < end) {
$f(vs.borrow(i));
i = i + 1;
}
}
```

Using `for_ref` and a label, we can write a lambda expression to pass `for_ref` that will escape the
loop, returning the first even number it finds:

```move
fun find_first_even(vs: vector<u64>): Option<u64> {
'result: {
for_ref!(&vs, |n| if (*n % 2 == 0) { return 'result option::some(*n)});
option::none()
}
}
```

This function will iterate `vs` until it finds an even number, and return that (or return
`option::none()` if no even number exists). This makes named labels a powerful tool for interacting
with control flow macros such as `for!`, allowing you to customize iteration behavior in those
contexts.

## Restrictions

To clarify program behavior, you may only use `break` and `continue` with loop labels, while
`return` will only work with block labels. To this end, the following programs produce errors:

```
fun bad_loop() {
'name: loop {
return 'name 5
// ^^^^^ Invalid usage of 'return' with a loop block label
}
}
fun bad_block() {
'name: {
continue 'name;
// ^^^^^ Invalid usage of 'break' with a loop block label
break 'name;
// ^^^^^ Invalid usage of 'break' with a loop block label
}
}
```

0 comments on commit 05df173

Please sign in to comment.