Optionals · Lesson 8.4

Coalesce exit

Source
Leave a function or loop with ?? return, ?? continue or ?? break when an optional is absent.
You'll need: Coalesce, Return, Break, Continue

Sometimes there is no sensible stand-in for a missing value. A bonus for a player who does not exist is not 0 — there is simply nothing more to compute. The fallback after ?? does not have to be a value: it can also be a way out. That turns the most common shape of optional code — "if it is missing, stop here" — into a single line.

Three ways out

Any of these three can follow ??:

let score = ScoreOf(players, number) ?? return 0;     // leave the function
let score = ScoreOf(players, number) ?? continue;     // skip to the next turn
let score = ScoreOf(players, number) ?? break;        // leave the loop

If the optional is present, score is the int inside it and the code below runs as normal. If it is none, control leaves right there, and the line never produces a value at all. That is why score is a plain int with no ?: by the next line, absence has already been dealt with.

WrittenWhen the optional is noneAllowed in
?? return xthe function returns xany function — plain ?? return in one with no result
?? continuethe loop moves on to its next turna for, while or loop
?? breakthe loop ends; code after it runsa for, while or loop

?? return: a guard at the top

?? return deals with the missing case first, so the rest of the function can be written as if the value were always there:

func Bonus(players: Player[..], number: int) -> int {
    let score = ScoreOf(players, number) ?? return 0;
    if score >= 20 {
        return 5;
    }
    return 1;
}
flowchart LR
    s["ScoreOf(players, number)"] --> q{"Present?"}
    q -- "yes" --> b["score is a plain int;<br/>the function goes on"]
    q -- "no" --> r["return 0<br/>— nothing below runs"]

#3 is not on the team, so Bonus(players, 3) returns 0 on its first line. #10 is, with 25, and earns 5.

?? continue and ?? break in a loop

Inside a loop, the same idea skips one turn or ends the loop. requested holds 7, 3, 10, 4, and #3 is unknown:

var total = 0;
for number in requested {
    let score = ScoreOf(players, number) ?? continue;
    total += score;
}

?? continue skips #3 and the loop goes on, so the total is 12 + 25 + 8 = 45.

var counted = 0;
for number in requested {
    let score = ScoreOf(players, number) ?? break;
    PrintLine("  #{} scored {}", number, score);
    counted++;
}

?? break stops at the first unknown number instead: #7 is printed, #3 ends the loop, and #10 and #4 are never looked at.

The program

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

Src/Main.rux
// The fallback after `??` does not have to be a value. It can also be a way
// out:
//
//     let score = ScoreOf(players, number) ?? return 0;     leave the function
//     let score = ScoreOf(players, number) ?? continue;     skip to the next turn
//     let score = ScoreOf(players, number) ?? break;        leave the loop
//
// If the optional is present, `score` is the `int` inside it and the code
// below runs as normal. If it is `none`, control leaves right there, and the
// line never produces a value at all. That is why `score` is a plain `int`
// with no `?`: by the next line, absence has already been dealt with.
import Io::PrintLine;

struct Player {
    number: int;
    score: int;
}

func ScoreOf(players: Player[..], number: int) -> int? {
    for player in players {
        if player.number == number {
            return player.score;
        }
    }
    return none;
}

// `?? return` is a guard at the top of a function: deal with the missing case
// first, then write the rest as if the value were always there.
func Bonus(players: Player[..], number: int) -> int {
    let score = ScoreOf(players, number) ?? return 0;
    if score >= 20 {
        return 5;
    }
    return 1;
}

func Main() -> int {
    let players = [
        Player { number: 4, score: 8 },
        Player { number: 7, score: 12 },
        Player { number: 10, score: 25 }
    ];
    let requested = [ 7, 3, 10, 4 ];

    PrintLine("bonus for #10: {}", Bonus(players, 10));
    PrintLine("bonus for #3:  {}", Bonus(players, 3));

    // `?? continue` skips a number nobody wears, and the loop goes on.
    var total = 0;
    for number in requested {
        let score = ScoreOf(players, number) ?? continue;
        total += score;
    }
    PrintLine("total, skipping unknown numbers: {}", total);

    // `?? break` stops at the first unknown number instead.
    var counted = 0;
    for number in requested {
        let score = ScoreOf(players, number) ?? break;
        PrintLine("  #{} scored {}", number, score);
        counted++;
    }
    PrintLine("counted {} before the first unknown number", counted);

    // Watch out: these exits only make sense where they are allowed. Outside
    // a loop, `?? continue` and `?? break` are errors, just as a bare
    // `continue;` would be.
    return 0;
}

Run it

cd Examples/Optionals/CoalesceExit
rux run
bonus for #10: 5
bonus for #3:  0
total, skipping unknown numbers: 45
  #7 scored 12
counted 1 before the first unknown number

Common mistakes

Leaving a loop that is not there.
Outside a loop, ?? continue and ?? break are errors, just as a bare continue; would be: error: 'continue' can only be used inside 'while', 'for', or 'loop'.
Returning none from a function that does not return an optional.
In a function declared -> int, ?? return none fails with error: 'none' needs an expected optional type, but found 'int'. Return a value of the function's own type, or make the function return int? — and then ? says the same thing more briefly.
Expecting ?? break to skip.
break ends the whole loop at the first none; it does not move on to the next item. Reach for ?? continue when the other items should still be processed.

Try it yourself

  1. Change requested so that the unknown number comes last. What do the two loops print now?
  2. Write Describe(players: Player[..], number: int) -> char8[..] that begins with ?? return "unknown" and then labels the score.
  3. Make the ?? continue loop also report how many numbers it skipped. Hint: count every turn before the ?? line and every success after it.

Learn more