Control Flow

if Expressions

An if expression runs a block of code only when a condition is true. The condition is evaluated once. If it is true, the block runs. If it is false, the block is skipped and execution continues after the if.

fn main() {
    let temperature = 31;

    if temperature > 30 {
        println!("It is hot.");
    }

    if temperature < 0 {
        println!("It is freezing.");
    }

    println!("Done.");
}
It is hot.
Done.

Syntax

if <condition> {
    <block>
}
        +-----------+
        | condition |
        +-----+-----+
              |
      +-------+-------+
      |               |
    true            false
      |               |
 +----v----+          |
 |  block  |          |
 +----+----+          |
      |               |
      +-------+-------+
              |
        next statement

The parts of an if:

Part Rule
if keyword Starts the expression
Condition Any expression of type bool
Block Curly braces are mandatory, even for a single statement
Parentheses Not required around the condition

The Condition Must Be a bool

Rust does not convert values to booleans. The condition must have type bool exactly. Integers, strings, Option values, and empty collections are never treated as "truthy" or "falsy".

// ❌ does not compile
fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}
error[E0308]: mismatched types
 --> main.rs:4:8
  |
4 |     if number {
  |        ^^^^^^ expected `bool`, found integer

Write the comparison you mean:

Other languages Rust
if (count) if count != 0
if (name) or if (!name.empty()) if !name.is_empty()
if (ptr) if option.is_some()
if (flag == true) if flag
fn main() {
    let count = 0;
    let name = "";

    if count != 0 {
        println!("count is non-zero");
    }
    if !name.is_empty() {
        println!("name is set");
    }
    println!("checked");
}
checked

Neither condition is true, so only the last line prints. When a variable is already a bool, use it directly. if flag == true is redundant, and clippy flags it.

Building Conditions

Any expression that produces a bool is a valid condition: comparisons, logical operators, method calls, function calls, and bool variables.

Form Example
Comparison age >= 18
Logical AND age >= 18 && has_ticket
Logical OR age < 13 || age > 65
Logical NOT !is_banned
Method returning bool name.starts_with("Fer")
Closure-based check digits.chars().all(|c| c.is_ascii_digit())
bool variable has_ticket
fn main() {
    let age = 25;
    let has_ticket = true;
    let is_banned = false;
    let name = String::from("Ferris");
    let digits = "2024";
    let maybe: Option<i32> = Some(7);

    if age >= 18 && has_ticket && !is_banned {
        println!("entry allowed");
    }
    if age < 13 || age > 65 {
        println!("discounted ticket");
    }
    if name.starts_with("Fer") {
        println!("name starts with Fer");
    }
    if digits.chars().all(|c| c.is_ascii_digit()) {
        println!("all digits");
    }
    if maybe.is_some() {
        println!("maybe holds a value");
    }
    if name.is_empty() {
        println!("never printed");
    }
}
entry allowed
name starts with Fer
all digits
maybe holds a value

Both Sides of a Comparison Must Have the Same Type

Comparison operators do not convert between numeric types. Comparing an integer with a float, or an i32 with an i64, is a compile-time error.

// ❌ does not compile
fn main() {
    let x = 5;

    if x == 5.0 {
        println!("five");
    }
}
error[E0277]: can't compare `{integer}` with `{float}`
 --> main.rs:4:10
  |
4 |     if x == 5.0 {
  |          ^^ no implementation for `{integer} == {float}`
  |
  = help: the trait `PartialEq<{float}>` is not implemented for `{integer}`
// ❌ does not compile
fn main() {
    let small: i32 = 5;
    let big: i64 = 5;

    if small == big {
        println!("equal");
    }
}
error[E0308]: mismatched types
 --> main.rs:5:17
  |
5 |     if small == big {
  |        -----    ^^^ expected `i32`, found `i64`
  |        |
  |        expected because this is `i32`
  |
help: you can convert `small` from `i32` to `i64`, matching the type of `big`
  |
5 |     if i64::from(small) == big {
  |        ++++++++++     +

Convert one side explicitly:

fn main() {
    let small: i32 = 5;
    let big: i64 = 5;

    if i64::from(small) == big {
        println!("equal");
    }
}
equal

The Condition Is Evaluated Once, Left to Right

When a condition contains && or ||, evaluation stops as soon as the result is known. The right-hand operand of && is skipped when the left side is false. The right-hand operand of || is skipped when the left side is true.

fn is_ready(label: &str, answer: bool) -> bool {
    println!("checking {label}");
    answer
}

fn main() {
    if is_ready("first", true) && is_ready("second", false) && is_ready("third", true) {
        println!("all ready");
    } else {
        println!("not ready");
    }

    if is_ready("a", true) || is_ready("b", true) {
        println!("at least one ready");
    }
}
checking first
checking second
not ready
checking a
at least one ready

"third" and "b" are never checked. Functions with side effects inside a condition run only when the evaluation reaches them.

Braces Are Required

The block after the condition must be wrapped in { }. Rust has no single-statement form like if (x) statement; in C.

// ❌ does not compile
fn main() {
    let score = 82;

    if score >= 50
        println!("pass");
}
error: expected `{`, found `println`
 --> main.rs:5:9
  |
5 |         println!("pass");
  |         ^^^^^^^ expected `{`
  |
note: the `if` expression is missing a block after this condition
 --> main.rs:4:8
  |
4 |     if score >= 50
  |        ^^^^^^^^^^^
help: you might have meant to write this as part of a block
  |
5 |         { println!("pass"); }
  |         +                   +

Mandatory braces remove a whole category of bugs in which a second indented line looks like part of the if but is not.

Parentheses Are Not Needed

The condition does not need parentheses. Adding them compiles, but the compiler warns about them.

fn main() {
    let score = 82;

    if (score >= 50) {
        println!("pass");
    }
}
warning: unnecessary parentheses around `if` condition
 --> main.rs:4:8
  |
4 |     if (score >= 50) {
  |        ^           ^
  |
  = note: `#[warn(unused_parens)]` (part of `#[warn(unused)]`) on by default
help: remove these parentheses
  |
4 -     if (score >= 50) {
4 +     if score >= 50 {
  |

Parentheses are still useful inside a condition to group && and ||, for example if (a || b) && c. Only the outermost pair around the whole condition is flagged.

An if Without else Evaluates to ()

An if is an expression, so it has a value. When there is no else branch, the value is always the unit value (). This is true whether the condition was true or false.

fn main() {
    let x = 5;
    let result = if x > 3 {
        println!("x is greater than 3");
    };
    println!("{result:?}");

    let skipped = if x > 10 {
        println!("never printed");
    };
    println!("{skipped:?}");
}
x is greater than 3
()
()

Because the missing else branch can only produce (), the block of an if without else must also produce (). A block that ends in a value, such as { 5 }, is rejected.

if as a Statement

Most if expressions are used as statements: the value is () and is discarded. An if in statement position does not need a trailing semicolon. A semicolon after the closing brace is allowed and has no effect.

fn main() {
    let n = 7;

    if n % 2 == 1 {
        println!("odd");
    };

    if n > 5 {
        println!("big");
    }
    println!("end");
}
odd
big
end