Control flow · Lesson 3.7

Break

Source
Leave a loop early with break, and tell afterwards whether it ran.
You'll need: Loop

A search can stop as soon as it finds what it is looking for. There is no point checking the rest of the haystack once the needle is in your hand. break leaves a loop immediately: the rest of the current pass is skipped, the condition is not tested again, and the program carries on with the first statement after the loop. It works in every loop — while, do-while, loop, and the for loop coming up later in this part.

Stopping a search early

The program looks for the smallest divisor of 91. It could try every candidate from 2 up to 90, but once one divides evenly there is no reason to keep going:

let number: int32 = 91;
var divisor: int32 = 2;
while divisor < number {
    if number % divisor == 0 {
        break;
    }
    divisor += 1;
}

2, 3, 4, 5 and 6 do not divide 91; 7 does, so the loop stops with divisor at 7 — after six candidates instead of eighty-nine.

How did the loop end?

A loop with a break can end two ways: its condition became false, or the break ran. Code after the loop often needs to know which, and it finds out by looking at the variables the loop left behind:

if divisor < number {
    PrintLine("{} = {} x {}", number, divisor, number / divisor);
} else {
    PrintLine("{} is prime", number);
}
flowchart LR
    test{"divisor < number?"} -- "true" --> check{"number % divisor == 0?"}
    check -- "yes: break" --> found["divisor < number:<br/>a divisor was found"]
    check -- "no" --> step["divisor += 1"]
    step --> test
    test -- "false" --> prime["divisor == number:<br/>no divisor, prime"]

If the loop ran out of candidates, divisor ended equal to number; if break ran, it is still below. One comparison tells the two endings apart.

break leaves one loop

With loops inside loops, break leaves only the loop it is written in — the innermost one around it. Here it ends one row of a triangle, and the outer loop carries on with the next row as if nothing happened:

var row: int32 = 1;
while row <= 4 {
    var column: int32 = 1;
    while column <= 4 {
        if column > row {
            break;
        }
        Print("*");
        column += 1;
    }
    PrintLine();
    row += 1;
}

Row 1 prints one star before column > row stops the inner loop, row 2 prints two, and so on. To leave an outer loop from inside an inner one, you need a label — the subject of Label.

break in a do-while

In a do-while, break also skips the test at the bottom:

var attempts: int32 = 0;
do {
    attempts += 1;
    if attempts == 3 {
        break;
    }
} while attempts < 10;
PrintLine("gave up after {} attempts", attempts);

The condition would allow ten attempts, but the break ends the loop at three.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// `break` leaves a loop immediately. The rest of the current pass is skipped, the condition is
// not tested again, and the program carries on with the first statement after the loop. It works
// in every loop: `while`, `do`-`while` and `loop` alike.
//
// A loop with a `break` can end two ways: its condition became false, or the `break` ran. Code
// after the loop often needs to know which, and it finds out by looking at the variables the
// loop left behind.
//
// `break` belongs inside a loop. Anywhere else it is an error:
//
//     error: 'break' can only be used inside 'while', 'for', or 'loop'
import Io::{ Print, PrintLine };

func Main() -> int {
    // Searching for the smallest divisor of 91. The loop could try every candidate below 91, but
    // once one divides evenly there is no reason to keep going.
    let number: int32 = 91;
    var divisor: int32 = 2;
    while divisor < number {
        if number % divisor == 0 {
            break;
        }
        divisor += 1;
    }
    // Reaching `number` means the condition ended the loop and no divisor was found.
    if divisor < number {
        PrintLine("{} = {} x {}", number, divisor, number / divisor);
    } else {
        PrintLine("{} is prime", number);
    }

    // `break` leaves only the loop it is written in. Here it ends one row of the triangle; the
    // outer loop carries on with the next row as if nothing happened.
    var row: int32 = 1;
    while row <= 4 {
        var column: int32 = 1;
        while column <= 4 {
            if column > row {
                break;
            }
            Print("*");
            column += 1;
        }
        PrintLine();
        row += 1;
    }

    // In a `do`-`while`, `break` also skips the test at the bottom.
    var attempts: int32 = 0;
    do {
        attempts += 1;
        if attempts == 3 {
            break;
        }
    } while attempts < 10;
    PrintLine("gave up after {} attempts", attempts);
    return 0;
}

Run it

cd Examples/ControlFlow/Break
rux run
91 = 7 x 13
*
**
***
****
gave up after 3 attempts

Common mistakes

break outside a loop.
break belongs inside a loop. Anywhere else it fails with error: 'break' can only be used inside 'while', 'for', or 'loop'. In particular, it cannot leave an if — an if block is not a loop.
Expecting break to leave every loop.
In nested loops, a plain break ends only the innermost one. The outer loop carries on. Use a label to leave an outer loop.
Not checking how the loop ended.
After a search loop, the variables may describe "found" or "not found". Test them before using the result — otherwise a prime such as 97 would be reported as 97 = 97 x 1.

Try it yourself

  1. Change number to 97 and check that the program reports it as prime.
  2. Find the first multiple of 7 above 100 with a loop and break.
  3. Make the triangle print five rows, then make it print the triangle upside down.

Learn more