Else if
if and else choose between two paths. Many decisions have more than two: a grade is an A, a B, a C or a fail; a temperature is a heat warning, a frost warning or nothing at all. else if adds another condition to an if, and a chain of them chooses among as many cases as you need.
A chain of conditions
let score: int32 = 85;
if score >= 90 {
PrintLine("{} earns an A", score);
} else if score >= 80 {
PrintLine("{} earns a B", score);
} else if score >= 70 {
PrintLine("{} earns a C", score);
} else {
PrintLine("{} needs another try", score);
}
The chain tests its conditions from the top, runs the block of the first one that holds, and skips everything after it. A final plain else catches whatever no condition matched. So at most one block in a chain runs — exactly one, when the chain ends with else.
flowchart LR
s(["`score = 85`"]) --> a{"`score >= 90?`"}
a -- "yes" --> A["an A"]
a -- "no" --> b{"`score >= 80?`"}
b -- "yes" --> B["a B"]
b -- "no" --> c{"`score >= 70?`"}
c -- "yes" --> C["a C"]
c -- "no" --> E["another try"]85 fails the first test and passes the second, so the chain prints 85 earns a B and never looks at >= 70.
Each test may assume the ones above it failed
By the time the chain reaches score >= 80, the score is already known to be below 90 — otherwise the first block would have run. So the chain needs no score >= 80 && score < 90. Each condition only has to draw the next line, not repeat the ones above it.
Order is part of the meaning
Because the first match wins, the order of a chain changes what it does. Here are the same conditions, loosest first:
if score >= 70 {
PrintLine("reordered chain: a C");
} else if score >= 80 {
PrintLine("reordered chain: a B");
} else if score >= 90 {
PrintLine("reordered chain: an A");
}
85 is also at least 70, so the first test already holds and the chain stops there. Worse, the >= 80 and >= 90 blocks can now never run for anyone: every score that would reach them has already been caught by >= 70. The compiler cannot tell this from a deliberate choice, so it is up to you. A good rule for ranges like these: test the narrowest case first.
A chain may run nothing
Without a final else, it is possible for no condition to hold:
let temperature: int32 = 18;
if temperature > 30 {
PrintLine("heat warning");
} else if temperature < 0 {
PrintLine("frost warning");
}
PrintLine("{} degrees needs no warning", temperature);
Leave out the else only when "nothing happens" is a real answer, as it is here.
A chain, or separate ifs?
A chain and a row of separate if statements look alike but behave differently:
| Shape | Tests | Blocks that run |
|---|---|---|
if … else if … else if … | until the first condition that holds | at most one |
if … then if … then if … | every condition, every time | every one whose test holds |
if temperature > 10 {
PrintLine("above 10");
}
if temperature > 15 {
PrintLine("above 15");
}
Both of these print, because they are two separate statements. Written as a chain, only above 10 would.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// `else if` adds another condition to an `if`, and a chain of them chooses among many cases. The
// chain tests its conditions from the top, runs the block of the first one that holds, and skips
// everything after it. So at most one block in a chain runs, and a final plain `else` catches
// whatever no condition matched.
//
// Because the first match wins, the order of a chain is part of its meaning. The two chains below
// test the same score with the same conditions, in different orders, and disagree.
import Io::PrintLine;
func Main() -> int {
let score: int32 = 85;
// Each test may assume the ones above it failed: by the `>= 80` line, the score is known to be
// below 90, so the chain needs no `score >= 80 && score < 90`.
if score >= 90 {
PrintLine("{} earns an A", score);
} else if score >= 80 {
PrintLine("{} earns a B", score);
} else if score >= 70 {
PrintLine("{} earns a C", score);
} else {
PrintLine("{} needs another try", score);
}
// The same conditions, loosest first. 85 is also at least 70, so the first test already
// holds, the chain stops there, and the `>= 80` and `>= 90` blocks can never run for anyone.
if score >= 70 {
PrintLine("reordered chain: a C");
} else if score >= 80 {
PrintLine("reordered chain: a B");
} else if score >= 90 {
PrintLine("reordered chain: an A");
}
// Without a final `else`, a chain may run nothing at all.
let temperature: int32 = 18;
if temperature > 30 {
PrintLine("heat warning");
} else if temperature < 0 {
PrintLine("frost warning");
}
PrintLine("{} degrees needs no warning", temperature);
// Compare a chain with separate `if` statements: separate statements test every condition,
// and each one that holds runs. Both of these print, where a chain would print only the first.
if temperature > 10 {
PrintLine("above 10");
}
if temperature > 15 {
PrintLine("above 15");
}
return 0;
}
Run it
cd Examples/ControlFlow/ElseIf
rux run
85 earns a B
reordered chain: a C
18 degrees needs no warning
above 10
above 15
Common mistakes
Try it yourself
- Change
scoreto95,72and40and predict each line before you run. - Add an
A+grade for scores of 97 and above. Where in the chain does it have to go? - Rewrite the temperature check so it prints
"mild"when there is no warning, using a finalelse. - Turn the two separate
ifs at the end into one chain, and check that only one line prints.