Rust Conditional Control (if, else if, else, match) Tutorial

In this Rust tutorial we learn how to control the flow of our Rust application with if, else if, else, and match conditional statements.

We also cover how to nest conditional statements inside other conditional statements as well as how to conditionally assign a value to a variable if the if let expression.

What is conditional control flow

Rust allows us to control the flow of our program through specific expressions that evaluate conditions. Based on the result of these conditions, Rust will execute sections of specified code.

As an example, let’s consider a application with a dedicated area for members. Before a user can gain access to the member area, they need to provide the correct login credentials.

If they provide the correct credentials, they are allowed into the exclusive member area. Otherwise, they will be given the option to try again or reset their password if they have forgotten it.

Based on this logic, we can imagine that our code would look something like this.

Example:
if username == db_username && password == db_password
    redirect to member area
otherwise
    if forgot_password
        reset password

    try_again

Our actual code will not look exactly like the example above, but it serves to give you an example of what conditional control is.

We control the flow of our program with the following statements, in combination with relational comparison and logical operators.

  • if
  • else if
  • else
  • match

The if conditional statement

We give an if statement a condition to evaluate. If the condition proves true, the compiler will execute the if statement’s code block.

If the condition proves false, the compiler will move on to code below the if statement’s code block.

An if statement is written with the keyword if , followed by the condition(s) we want it to evaluate.

Then, we specify a code block with open and close curly braces. Inside the code block we write one or more expressions to be executed if the evaluation proves true.

Syntax: if conditional
if condition {
    // code block to execute
    // if condition proves true
}

Note that an if statement is not terminated with a semicolon. The closing curly brace will tell the compiler where the if statement ends.

Example: if conditional
fn main() {

    let a = 5;

    if a < 10 {
        println!("Evaluation proved true");
    }
}

In the example above, our variable a has a value less than 10, so the condition proves true. The compiler then prints the message inside the if statement’s code block.

When the compiler has finished executing the if statement, it will move on to any code after it. The same is true when our if statement proves false, the compiler will move on to the next piece of code outside and below the if statement.

Example: move to code outside and below if
fn main() {

    let a = 5;

    if a > 10 {
        println!("Execute only if the condition proves true");
    }

    println!("Code outside and below if statement");
}

In the example above, our if condition proves false. The compiler then moves on to the next section of code after the if statement.

How to evaluate multiple if conditions

We can evaluate more than one condition in an if statement with the help of the two logical operators && and ||.

Conditional AND is used to test if one condition and another proves true. Both conditions must prove true before the code will execute.

To evaluate multiple conditions with AND, we separate them with the && operator.

Syntax: conditional and
if condition_1 && condition_2 {
    // code block to execute if
    // both conditions prove true
}
Example: conditional and
fn main() {

    let a = 5;

    if a > 0 && a < 10 {
        println!("Both conditions are true");
    }
}

In the example above, both of our conditions prove true so the code inside the if code block executes.

The conditional OR is used to test if one condition or another proves true. Only one of the conditions must prove true before the code will execute.

To evaluate multiple conditions with OR, we separate them with the || operator.

Syntax: conditional or
if condition_1 && condition_2 {
    // code block to execute if
    // one condition or the
    // other proves true
}
Example: conditional or
fn main() {

    let a = 5;

    if a > 5 || a < 10 {
        println!("One of the conditions are true");
    }
}

In the example above, the first condition proves false, but the second condition proves true so the compiler is allowed to execute the code in the code block.

The conditional else if ladder

When we have multiple types of conditions that do not fit within a single if condition, we can write more if statements and combine them into an else if ladder.

The compiler will move to evaluate each if statement in the ladder when it’s done with previous one.

To write a conditional else if statement, we add another if statement below our first, separated by the else keyword.

Syntax: else if conditional
if condition {
    // execution block
}
else if condition_2 {
    // execution block
}
Example: else if conditional
fn main() {

    if false {
        println!("Primary if statement");
    } else if true {
        println!("Secondary if statement");
    }
}

In the example above, we add a secondary if statement at the end of the first, separated by the else keyword.

Note that an else if statement can only follow an if statement, it cannot stand on its own. The first conditional must always be an if.

We can have as many else if statements in a ladder as we need.

Example: multiple else if conditionals
fn main() {

    if false {
        println!("Primary if statement");
    } else if false {
        println!("Secondary if statement");
    } else if true {
        println!("Tertiary if statement");
    }

}

The conditional fallback else statement

The else statement works as a catch-all fallback for anything that isn’t covered by an if statement or else if ladder.

Because it acts as a catch-all, the else statement doesn’t have a conditional block, only an execution code block.

Syntax: else catch-all fallback
if condition {
    // execution block
}
else {
    // catch-all block
}
Example: else catch-all fallback
fn main() {

    let number = 5;

    if number == 10 {
        println!("if statement");
    } else {
        println!("catch-all else fallback");
    }

}

In the example above, the else statement will execute any result other than number == 10. Because number is not 10, the compiler executes the else execution code block.

How to nest if, else if and else statements

We can place if statements inside other if statements, a technique known as nesting. The compiler will perform the evaluations hierarchically, that’s to say, it will start out the outer if statement and move inwards.

To nest an if statement, we simply write it inside the execution code block of another if statement.

Syntax: nested conditionals
if outer_condition {

    // outer execution block

    if inner_condition {
        // inner execution block
    }
}
Example: nested conditionals
fn main() {

    let number = 5;

    if number < 10 {
        // if outer condition proves
        // true evaluate the inner if
        if number > 0 {
            println!("Inner if statement");
        }
    }
}

In the example above, our outer if statement condition proves true. The compiler then evaluates the inner if statement and executes its code block if its condition proves true.

Nested if statements are used when we want to evaluate conditions only if other conditions are true.

Note that if you find yourself nesting more than 3 levels deep, you should consider finding a different method. Perhaps a loop.

The conditional match statement

The match statement will check a single value against a list of values.

If you’re familiar with a languages in the C family, it would be similar to a switch statement, although the syntax is different.

Let’s take a look at the syntax before we explain.

Syntax:
let expression_result = match main_value {

    value_to_match => { execution_statements },
    value_to_match => { execution_statements },
    value_to_match => { execution_statements },

    _ => { default_execution_statements }
};

There’s a lot going on here and it may seem confusing at first, so let’s take it step by step.

1. The match statement returns a value, so we can assign the whole statement to a variable to get the result.

Example:
  let expression_result =

2. Now we write the match statement itself. We use the keyword match, followed by the main value we are going to compare any other values to. Finally, we define a code block.

Note that the code block is terminated with a semicolon after the closing curly brace.

Example:
let expression_result = match main_value {

};

3. Inside the code block we will write the values that we want to try and match with the main value.

First, we specify the value we want to match, followed by a => operator. Then, inside another code block, we specify the execution statements if that value matches with the main value.

Each of these values are separated with a comma, and we can have as many as we need.

The final value to match is simply an underscore. The single, standalone, underscore in a match statement represents a catch-all situation if none of the values matched to the main value. Think of it as an else statement.

Now, let’s see a practical example of the match statement.

Example:
fn main() {

    let grade = "B";

    let _result = match grade {
        "A" => { println!("Fantastic, you got an A!"); },
        "B" => { println!("Great job, you got a B!"); },
        "C" => { println!("Good job, you got a C"); },
        "D" => { println!("You got a D, you passed"); },
        "F" => { println!("Sorry, you failed"); },

        _ => { println!("Unknown grade, please see the teacher"); }
    };
}

In the example above, we give a student a grade and store it in a variable. Then we check to see which grade score in the match statement the student’s score matches.

If the compiler finds a match on the left of the => operator, it will execute whatever statement is on the right of the => operator.

In this case, it matched to “B” on the left and so it printed the string “Great job, you got a B!”.

Note that we don’t use the result variable in the example above. This is simply because we don’t need to, the execution statement of the match already prints a message.

If we want to return an actual value, we specify the value instead of an action like println.

Example:
fn main() {

    let grade = "A";

    let result = match grade {
        "A" => "Excellent!",
        "B" => "Great!",
        "C" => "Good",
        "D" => "You passed",
        "F" => "Sorry, you failed",
        _ => "Unknown Grade"
    };

    println!("Grade: {} - {}", grade, result);
}

In the example above, we replaced the println!() statements with simple string values.

When the compiler finds a match on the left of the => operator, it will return the value on the right of the => into the result variable.

This time, the compiler matched to “A” on the left, so it stored the string “Excellent!” into the result variable.

We then used it in the println!() below the match statement.

Conditional variable assignment (if let)

We can conditionally assign a value to a variable with an if let expression. To do this, we simply write the if statement where the value should be in the variable initialization.

Syntax:
let variable_name = if condition {
    // value of variable, example:
    // "Hello World"
} else {
    // value of variable, example:
    // "Hello there"
};

It’s important to note a few differences here to a normal if statement.

  1. We must have both an if and else statement.
  2. The execution expressions must provide a value to be assigned to the variable.
  3. The individual statements in the code blocks are not terminated with a semicolon.
  4. The if statement itself is terminated with a semicolon after the else closing curly brace.
Example:
fn main() {

    let number = if true {
        1
    } else {
        2
    };

    println!("Number: {}", number);
}

In the example above, the if statement will prove true so the value assigned to the number variable is 1.

Let’s see another example.

Example:
fn main() {

    let is_birthday = true;

    let message = if is_birthday {
        "Happy birthday"
    } else {
        "No cake for you"
    };

    println!("{}", message);
}

As we know, a variable can only hold one type of value. If we try to assign different types of values to it through the if statement, the compiler will raise an error.

Example:
fn main() {

    let number = if true {
        1
    } else {
        "Hello World"
    };

    println!("Number: {}", number);
}

In the example above, the values we want to assign is of different types.

Eclipse will highlight the potential error for us before we compile. If we go ahead and compile anyway, the following error is raised.

Output:
if and else have incompatible types

note: expected type `{integer}`
         found type `&str`

error: aborting due to previous error

The Rust ternary operator

Rust does not have a ternary operator anymore.

In many languages, such as C or Python we can write a shortened version of a simple if/else statement, known as the ternary operator.

Example: python ternary
# this if statement:
if 1==1:
    print("if execution")
else:
    print("else execution")

# would convert to ternary like this:
print("if execution") if 1==1 else print("else execution")

Rust, removed the ternary operator back in 2012 . The language doesn’t support a ternary operator anymore.

Summary: Points to remember

  • We can control the flow of our application with conditional logic through if, else if, else and match statements.
  • The if and else if conditional statements must include a condition to evaluate.
  • The else statement is a catch-all fallback to if and else if and as such cannot have a condition.
  • We can nest if statements inside other if statements. The compiler will evaluate them hierarchically.
  • The match statement is used when we want to compare one main value against multiple other values.
    • The match statement is similar to a switch statement in many other languages such as C,C++.
  • We can conditionally set the value of a variable with an if statement with an if let expression.
    • An if let expression places the if statement as the value of a variable declaration.
    • An if let expression is terminated with a semicolon after the code block closing brace.
    • Values inside the code blocks of an if let expression are not terminated with a semicolon.