# 4.7 Introduction to if expressions > Rust's if: bool-only conditions, mandatory braces, else chains, and the twist that if produces a value. Source: https://learnrust.net/chapter-4/introduction-to-if-expressions/ Until now, your programs have run every statement, top to bottom, every time. Useful programs are choosier. The chooser is `if`, it works the way you'd guess for about three paragraphs, and then Rust plays the card it's been palming since lesson [1.11](@/chapter-1/expressions-and-statements.md). ## The basic form ```rust fn main() { let temperature = 38; if temperature > 30 { println!("It's hot out."); } println!("Weather report complete."); } ``` ``` It's hot out. Weather report complete. ``` `if`, then a condition, then a brace-wrapped block that runs only when the condition is `true`. The condition must be an actual `bool` (lesson [4.6](@/chapter-4/boolean-values.md) showed the compiler enforcing that), and the comparison operators are how conditions usually get made. Two syntax notes for readers arriving from other languages, both enforced rather than suggested. There are no parentheses around the condition; write them anyway and the compiler tidily objects: ``` warning: unnecessary parentheses around `if` condition --> src/main.rs:3:8 | 3 | if (temperature > 30) { | ^ ^ | = note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default help: remove these parentheses | 3 - if (temperature > 30) { 3 + if temperature > 30 { | ``` And the braces are mandatory, even for a single statement. C-family languages make braces optional there, which spawned a famous bug genus: add a second statement under a brace-less `if`, and it silently runs *unconditionally*, indentation notwithstanding (a vulnerability in Apple's TLS code, the celebrated "goto fail" bug, was approximately this). Rust deletes the genus: no braces, no compile. ## else and else if `else` supplies the other path, and `else if` chains more conditions, checked top to bottom, first match wins: ```rust fn main() { let n = -4; if n > 0 { println!("{n} is positive"); } else if n < 0 { println!("{n} is negative"); } else { println!("{n} is zero"); } } ``` ``` -4 is negative ``` So far, so familiar. Now the twist. ## if is an expression In Rust, `if` doesn't just *direct traffic*; the whole `if`/`else` evaluates to a value, which means it can sit anywhere a value can: ```rust fn main() { let score = 87; let grade = if score >= 60 { "pass" } else { "fail" }; println!("{grade}"); } ``` ``` pass ``` You knew this was coming. Blocks are expressions whose value is their tail expression (lesson [1.11](@/chapter-1/expressions-and-statements.md)); an `if`/`else` picks *which block runs*, so its value is the chosen block's value. `{ "pass" }` and `{ "fail" }` are blocks with tail expressions; the `if` selects one; `grade` receives it. The machinery is old, only the application is new. Two rules keep it sound, and both are enforced with errors you can now read fluently. First, the arms must agree on type, because `grade` has to be *some* one type at compile time: ```rust let grade = if score >= 60 { "pass" } else { 0 }; ``` ``` error[E0308]: `if` and `else` have incompatible types --> src/main.rs:3:50 | 3 | let grade = if score >= 60 { "pass" } else { 0 }; | ------ ^ expected `&str`, found integer | | | expected because of this ``` Second, value-position `if` requires an `else`. Without one, what would `grade` be when the condition is false? The compiler declines to invent an answer: ```rust let grade = if score >= 60 { "pass" }; ``` ``` error[E0317]: `if` may be missing an `else` clause --> src/main.rs:3:17 | 3 | let grade = if score >= 60 { "pass" }; | ^^^^^^^^^^^^^^^^^------^^ | | | | | found here | expected `&str`, found `()` | = note: `if` expressions without `else` evaluate to `()` = help: consider adding an `else` block that evaluates to the expected type ``` (Read that note line with a 1.11 eye: a lone `if` is fine as a *statement*, where its `()` bothers no one, exactly like your first example in this lesson.) If you've used C-family languages, you may recognize what this replaces: the ternary operator `cond ? a : b`, a separate compressed syntax for "choose a value." Lesson [1.10](@/chapter-1/literals-and-operators.md) promised its obituary, and here it is: Rust's `if` already *is* an expression, so no second syntax is needed. One construct, statement or value, your choice. {% callout(kind="best", title="Best practice") %} Use the expression form when both arms exist to produce a value (`let label = if ... { a } else { b };` beats declaring a `mut` and assigning in branches, on the same reasoning as lesson [1.12](@/chapter-1/developing-your-first-program.md)'s preferred solution). Use the statement form when the branches exist to *do* things. When an expression-form `if` grows past a line or two per arm, let it be a statement again; readability outranks cleverness. {% end %} ## An old promise, kept Lesson [1.4](@/chapter-1/variables-mutability-initialization.md)'s warning box told you `=`/`==` confusion is a classic bug in other languages and promised this lesson would show you Rust's defense. In C++, `if (x = 5)` compiles: it *assigns* 5, the assignment produces 5, 5 is truthy, the branch always runs, and the bug ships. Watch the same typo here: ```rust fn main() { let mut x = 3; if x = 5 { println!("five"); } } ``` ``` error[E0308]: mismatched types --> src/main.rs:3:8 | 3 | if x = 5 { | ^^^^^ expected `bool`, found `()` | help: you might have meant to compare for equality | 3 | if x == 5 { | + ``` Two lessons conspired: assignment is an expression producing `()` (lesson 1.11), and conditions must be `bool` (lesson 4.6), so the typo has nowhere to hide, and the help line even diagnoses your intent. This is the course's thesis in one error message: same human mistake, but here it's a labeled compile error instead of a shipped bug. ## Quiz time **Question #1** What does this print? ```rust fn main() { let age = 70; if age < 13 { println!("child ticket"); } else if age < 65 { println!("adult ticket"); } else { println!("senior ticket"); } } ```
Show solution ``` senior ticket ``` 70 fails `< 13`, fails `< 65`, and lands in the `else`. Note the chain's elegance: each `else if` only needs to handle what earlier conditions didn't catch, so "adult" is just `< 65`, not "≥ 13 and < 65."
**Question #2** Write a function `describe(n: i32) -> &'static str` that returns `"positive"`, `"negative"`, or `"zero"`, using `if` as an expression (one `let`-free function body). Don't worry about the unfamiliar return type spelling; it's "a string like a literal," properly explained in chapter 9, and the compiler will accept exactly this spelling.
Show solution ```rust fn describe(n: i32) -> &'static str { if n > 0 { "positive" } else if n < 0 { "negative" } else { "zero" } } fn main() { println!("{}", describe(12)); println!("{}", describe(-3)); println!("{}", describe(0)); } ``` The whole `if`/`else if`/`else` is the function's tail expression; each arm's tail is a string. No `return`, no temporary variable, every path produces a value.
**Question #3** Predict the compiler's reaction, precisely: ```rust fn main() { let logged_in = false; let message = if logged_in { "welcome" }; println!("{message}"); } ```
Show solution E0317: `if` may be missing an `else` clause. In value position, the false case must produce something, and `()` doesn't match `"welcome"`'s type. Fix: add `else { "please log in" }` (or any `&str`).
Decisions unlocked. Next, the type that's been hiding inside every string you've printed: `char`, where Rust's Unicode story begins.