Control Flows

Rust provides two types of expressions for control flows: branching expressions and loop expressions.

if expression

An if expression evaluates a boolean predicate to decide which branch to execute. It may have one or two branches. If it has two branches, the second branch follows the else keyword. Each branch must be a block, i.e., enclosed in a matching pair of braces.


# #![allow(unused_variables)]
#fn main() {
let x = 1;
let y;
if x >= 0 {
    y = x;
} else {
    y = -x;
}
assert!(y >= 0);
#}

Since if is an expression, it has a value, which is the value of the block that it executes. If a block ends with an expression, its value is that of the expression; otherwise, its value is ().


# #![allow(unused_variables)]
#fn main() {
let x = 1;
let y = if x >= 0 {
    x
} else {
    -x
};
assert!(y >= 0);
#}

Since an expression must have a unique type decidable at compile time, both branches of an if expression must have the same type. If the else branch is missing, its type is the unit type ().


# #![allow(unused_variables)]
#fn main() {
let x = 1;
// Error: two branches have different types.
let y = if x >= 0 {
    x
} else {
    "Hello"
};
#}

# #![allow(unused_variables)]
#fn main() {
let x = 1;
// Error: the `else` branch is missing and so has the unit type `()`
let y = if x >= 0 {
    x
} ;
#}

# #![allow(unused_variables)]
#fn main() {
let x = 1;
// OK: the `else` branch is missing and so has the unit type `()`.
// The true branch does not end with an expression, so it has also the unit type too.
// Note that the type of `y` is the unit type.
let y = if x >= 0 {
    x;
};
#}

Rust also provides the match expression for branching, which we will describe in a later chapter.

Loop expressions

loop expression

The loop expression executes its block infinitely. A program can break out of the loop by the break expression.


# #![allow(unused_variables)]
#fn main() {
let mut x = 0;
loop {
    x += 1;
    if x > 9 {
        break;
    }
}
assert_eq!(x, 10);
#}

The break expression may be followed by an expression, which becomes the value of the loop expression.


# #![allow(unused_variables)]
#fn main() {
let mut x = 0;
let y = loop {
    x += 1;
    if x > 9 {
        break x;
    }
};
assert_eq!(y, 10);
#}

while expression

The while expression takes a predicate and executes its block indefinitely as long as the predicate is true.


# #![allow(unused_variables)]
#fn main() {
let mut x = 0;
while x < 10 {
    x += 1;
};
assert_eq!(x, 10);
#}

A program may break out of a while loop by the break expression. Or it may execute the continue expression to stop executing the current iteration and jump to the predicate of the loop.


# #![allow(unused_variables)]
#fn main() {
let mut x = 0;
while x < 10 {
    x += 1;
    if x % 2 == 0 {
        continue;
    }
    println!("{}", x);
};
assert_eq!(x, 10);
#}

for expression

The most common, versatile loop expression is the for expression. A for expression loops over a collection of items.


# #![allow(unused_variables)]
#fn main() {
for i in 0..10 {
    println!("{}", i);
}
#}

0..10 is syntactic sugar for the Range type, which implements the std::iter::IntoIterator trait. In fact, a program may use the for expression on any expression that implements the std::iter::IntoIterator trait. For example:


# #![allow(unused_variables)]
#fn main() {
let a = [1, 2, 3];
let v = vec![1, 2, 3];
for i in a.iter() {
    println!("{}", i);
}
for i in &v { 
    println!("{}", i);
}
// Prints both the index and value.
for (index, value) in v.iter().enumerate() {
    println!("{}: {}", index, value);
}
#}