if-else
An if-else expression chooses between two blocks of code. If the condition is true, the if block runs. Otherwise, the else block runs. Exactly one of the two blocks runs, never both and never neither.
fn main() {
let number = 7;
if number % 2 == 0 {
println!("{number} is even");
} else {
println!("{number} is odd");
}
}
7 is odd
Syntax
if <condition> {
<block A>
} else {
<block B>
}
+-----------+
| condition |
+-----+-----+
|
+-------+-------+
| |
true false
| |
+----v----+ +----v----+
| block A | | block B |
+----+----+ +----+----+
| |
+-------+-------+
|
next statement
The else keyword goes directly after the closing brace of the if block. It takes no condition. The braces are mandatory on both blocks. Layout is flexible: else can sit on the same line as } (the standard rustfmt style) or on the next line, and short chains can be written on one line.
fn main() {
let n = 5;
if n > 3 {
println!("big");
}
else {
println!("small");
}
if n > 10 { println!("huge"); } else { println!("not huge"); }
}
big
not huge
else if Chains
When there are more than two possibilities, add else if branches between the if and the final else. Each else if has its own condition.
fn grade(score: u32) -> char {
if score >= 90 {
'A'
} else if score >= 80 {
'B'
} else if score >= 70 {
'C'
} else if score >= 60 {
'D'
} else {
'F'
}
}
fn main() {
for score in [95, 90, 85, 72, 60, 59, 0] {
println!("{score} -> {}", grade(score));
}
}
95 -> A
90 -> A
85 -> B
72 -> C
60 -> D
59 -> F
0 -> F
A chain has this shape:
if cond1 { ... }
else if cond2 { ... }
else if cond3 { ... }
else { ... }
else if is not a separate keyword. It is else followed by a new if expression, so these two forms are equivalent:
fn main() {
let a = 10;
let b = 20;
if a > b {
println!("a is greater");
} else if a < b {
println!("b is greater");
} else {
println!("equal");
}
if a > b {
println!("a is greater");
} else {
if a < b {
println!("b is greater");
} else {
println!("equal");
}
}
}
b is greater
b is greater
The flat else if form is the idiomatic one. The nested form adds one indentation level for every branch.
Evaluation Order
Conditions are tested from top to bottom. The first condition that is true selects its block. The rest of the chain is skipped, and the remaining conditions are never evaluated.
fn check(label: &str, answer: bool) -> bool {
println!(" testing {label}");
answer
}
fn main() {
println!("chain:");
if check("one", false) {
println!(" one");
} else if check("two", true) {
println!(" two");
} else if check("three", true) {
println!(" three");
} else {
println!(" none");
}
}
chain:
testing one
testing two
two
"three" is never tested, even though it would also have been true.
Order Matters When Conditions Overlap
When conditions overlap, put the most specific one first. Reversing the grade chain makes every passing score a D, because score >= 60 is true for all of them:
fn grade(score: u32) -> char {
if score >= 60 {
'D'
} else if score >= 70 {
'C'
} else if score >= 80 {
'B'
} else if score >= 90 {
'A'
} else {
'F'
}
}
fn main() {
println!("{}", grade(95));
}
D
This compiles without a warning. The compiler does not check whether a later condition can ever be reached.
One Chain vs Separate ifs
Each if that stands alone is tested independently. Branches joined with else are mutually exclusive.
fn main() {
let n = 15;
println!("separate ifs:");
if n % 3 == 0 {
println!(" divisible by 3");
}
if n % 5 == 0 {
println!(" divisible by 5");
}
println!("else-if chain:");
if n % 3 == 0 {
println!(" divisible by 3");
} else if n % 5 == 0 {
println!(" divisible by 5");
}
}
separate ifs:
divisible by 3
divisible by 5
else-if chain:
divisible by 3
Use separate ifs when several conditions can apply together. Use a chain when exactly one case should be selected.
The Final else
The last else is a catch-all. It runs when every earlier condition was false.
A chain may end without an else. In that case it is possible that no block runs at all:
fn main() {
let level = 3;
if level == 1 {
println!("low");
} else if level == 2 {
println!("medium");
} else if level == 3 {
println!("high");
}
println!("after the chain");
let level = 9;
if level == 1 {
println!("low");
} else if level == 2 {
println!("medium");
}
println!("after the second chain");
}
high
after the chain
after the second chain
With level = 9, no condition matches, nothing is printed from the chain, and execution continues after it.
A final else is required when the if chain is used to produce a value, because every possible path must then yield a result. The compiler rejects a chain used as a value that can fall through without one.
Real Example: Leap Years
Chains express rules with exceptions naturally. A year is a leap year if it is divisible by 400, or if it is divisible by 4 but not by 100. Checking the most specific rule first keeps each condition simple:
fn is_leap_year(year: u32) -> bool {
if year % 400 == 0 {
true
} else if year % 100 == 0 {
false
} else if year % 4 == 0 {
true
} else {
false
}
}
fn main() {
for year in [1900, 2000, 2023, 2024] {
if is_leap_year(year) {
println!("{year} is a leap year");
} else {
println!("{year} is not a leap year");
}
}
}
1900 is not a leap year
2000 is a leap year
2023 is not a leap year
2024 is a leap year
Scope of Variables in Branches
Each block has its own scope. A variable declared in one branch is not visible in another branch or after the if.
// ❌ does not compile
fn main() {
let n = 5;
if n > 3 {
let label = "big";
println!("{label}");
} else {
let label = "small";
println!("{label}");
}
println!("{label}");
}
error[E0425]: cannot find value `label` in this scope
--> main.rs:11:16
|
11 | println!("{label}");
| ^^^^^
|
help: the binding `label` is available in a different scope in the same function
--> main.rs:8:13
|
8 | let label = "small";
| ^^^^^
To use the result after the if, return it from the if expression and bind it with let, as covered in the if as an expression topic.
Common Syntax Errors
Rust has no elif and no elsif. The keyword sequence is else if.
// ❌ does not compile
fn main() {
let n = 5;
if n > 3 {
println!("big");
} elif n > 1 {
println!("medium");
}
}
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `n`
--> main.rs:6:12
|
6 | } elif n > 1 {
| ^ expected one of 8 possible tokens
An else must directly follow an if block. It cannot start a statement on its own:
// ❌ does not compile
fn main() {
let n = 5;
else {
println!("small");
}
}
error: expected expression, found keyword `else`
--> main.rs:4:5
|
4 | else {
| ^^^^ expected expression
if-else vs match
A chain of else if branches that all compare one value is often clearer as a match, which also checks that every case is covered:
fn describe(n: i32) -> &'static str {
match n {
i32::MIN..=-1 => "negative",
0 => "zero",
1..=9 => "small",
_ => "large",
}
}
fn main() {
for n in [-5, 0, 4, 100] {
println!("{n}: {}", describe(n));
}
}
-5: negative
0: zero
4: small
100: large
| Situation | Better fit |
|---|---|
Two outcomes based on one bool |
if / else |
| Conditions on different variables or unrelated tests | if / else if chain |
| Many cases for a single value, ranges, or enum variants | match |