Control Flow

Dear Computer

Chapter 10: Everything Revisited

Control Flow

As in other imperative languages, execution of a Rust program starts in the main function and proceeds sequentially from statement to statement. This linear flow can be made to branch or cycle back using conditional statements and loops.

If Expressions

Rust's if structure may be used as either a statement or an expression. This if statement categorizes a wavelength of visible light—measured in nanometers—into one of three broad colors:

Rust
if wavelength < 520 {
    println!("bluish");
} else if wavelength < 590 {
    println!("greenish");
} else {
    println!("reddish");
}

This form is very much like the conditional statements of C and Java. The primary difference is that conditions in Rust are not surrounded by parentheses, as in Ruby. The Rust compiler warns us against using them.

If Rust doesn't require parentheses, why are they required in C and Java? In those languages, curly braces around then- and else-blocks and loop blocks are optional if the blocks consist of a single statement. Without parentheses, conditions and unbraced single statements would run together and make parsing difficult. In Rust, braces are always required around a block, which means parentheses are never needed to separate the condition from the then block.

We could factor out the repeated println! calls from the conditional above by using an if expression that yields the color:

Rust
let color = if wavelength < 520 {
  "bluish"
} else if wavelength < 590 {
  "greenish"
} else {
  "reddish"
};
println!("{}", color);

When we have a conditional expression, a semi-colon is needed after the final branch to end the statement in which the expression appears. The expressions of the branches that produce the branch's value do not end in semi-colons.

While Statements

Rust's while loop behaves much like the while loops we have seen in other languages. Here a while loop is used to print the first 12 powers-of-two using bit-shifting:

Rust
let mut i = 0;
while i < 12 {
    println!("{}", 1 << i);
    i += 1;
}

The while loop is purely a statement, not an expression.

For-in Statements

The only for loop in Rust is the for-in loop that iterates through a collection or range. This for loop is equivalent to the while loop above:

Rust
for i in 0..12 {
    println!("{}", 1 << i);
}

Iterator i is not declared with let or mut. It exists in the loop's scope, but not in the surrounding scope. We can't access i after the loop finishes.

This for loop iterates through an array of integers:

Rust
let xs = [1, 11, 21, 1211, 111221];
for x in xs {
    println!("{}", x);
}

The Rust compiler implicitly inserts a call to get an iterator for the collection through which we are looping. The transformed loop looks like this:

Rust
let xs = [1, 11, 21, 1211, 111221];
for x in xs.iter() {
    println!("{}", x);
}

If we need an index, we can explicitly enhance the iterator with enumerate(). Each iteration then yields a tuple of the current index and value. The tuple may be destructured directly in the loop header:

Rust
let xs = [1, 11, 21, 1211, 111221];
for (index, x) in xs.iter().enumerate() {
    println!("{} -> {}", index, x);
}

Loop Expressions

Rust also supports a general loop expression. Unlike while and for, a loop expression doesn't have a syntactic place for a condition. It can be used to write infinite loops, which have a place in servers that run indefinitely. Such a loop would have this form:

Rust
loop {
  // wait for request
  // process request
}

It may also be used to write finite loops in which we decide when to exit using a break statement. Manual exits are useful in repetitions where we wish to test the condition in the middle of an iteration rather than at the beginning or end like we are forced to do with while and do-while loops. For example, this loop prints random comma-separated bytes until it reaches a number greater than 100:

Rust
loop {
    let number = random::<u8>();
    print!("{}", number);
    if number > 100 {
        break;
    }
    print!(", ");
}

The break condition is checked between printing the number and printing the separating comma, which means the final number won't be followed by a comma.

The break statement may be used to return a value to the surrounding context.

You may have been taught to avoid break statements. Be skeptical of dogma. The intent behind the dogma is a reasonable desire for readable and safe code. If a break produces readable and safe code, then use it.

← VariablesFunctions →