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); } #}