7.1 Conditional Statements

Conditional statements allow your program to make decisions based on specific criteria. Rust's primary decision-making construct is the if statement, similar to C's, but with some key differences.

7.1.1 Conditions Must Be Boolean

In Rust, conditions in if statements must explicitly be of type bool. Unlike C, where any non-zero integer is considered true, Rust does not perform implicit conversions from integers or other types to bool.

Comparison

C Code:

int number = 5;
if (number) {
    printf("Number is non-zero.\n");
}

In C, number being non-zero evaluates to true.

Rust Equivalent:

fn main() {
    let number = 5;
    if number != 0 {
        println!("Number is non-zero.");
    }
}

In Rust, you must explicitly compare number to zero to produce a bool.

Note: Attempting to use a non-boolean condition in Rust will result in a compile-time error, making your code safer by preventing unintended truthy or falsy evaluations.

7.1.2 The if Statement

The if statement in Rust executes code based on a condition that evaluates to true.

fn main() {
    let number = 5;
    if number > 0 {
        println!("The number is positive.");
    }
}

Key Points:

  • No Parentheses Required: Parentheses around the condition are optional in Rust.
  • Braces Are Required: Even for single-line bodies, braces {} are required.

Comparison with C

C Code:

int number = 5;
if (number > 0) {
    printf("The number is positive.\n");
}

In C, parentheses around the condition are required, but braces are optional for single statements.

7.1.3 else if and else

You can extend if statements with else if and else clauses to handle multiple conditions.

fn main() {
    let number = 0;
    if number > 0 {
        println!("The number is positive.");
    } else if number < 0 {
        println!("The number is negative.");
    } else {
        println!("The number is zero.");
    }
}

Key Points:

  • Conditions Checked Sequentially: Conditions are evaluated from top to bottom.
  • Exclusive Execution: Only the first branch where the condition evaluates to true is executed. If none of the conditions are met, the optional else branch is executed.
  • Syntax Simplicity: No parentheses are needed around conditions, and Rust does not require {} between else and if.

Comparison with C

C Code:

int number = 0;
if (number > 0) {
    printf("The number is positive.\n");
} else if (number < 0) {
    printf("The number is negative.\n");
} else {
    printf("The number is zero.\n");
}

Note: In C, both parentheses around conditions and braces for code blocks are required by syntax rules.

7.1.4 if as an Expression

In Rust, if statements can be used as expressions that return values. This allows you to assign the result of an if expression to a variable.

fn main() {
    let condition = true;
    let number = if condition { 10 } else { 20 };
    println!("The number is: {}", number);
}

Key Points:

  • Expression-Based: Both if and else branches must return values.
  • Type Consistency: All branches must return values of the same type.
  • No Ternary Operator: Rust uses if expressions instead of the ternary operator found in C.

When using if as an expression to assign a value, Rust requires that all possible conditions are covered. This means that you must include an else clause. Without an else clause, the if expression might not return a value in some cases, leading to a compile-time error.

Comparison with the Ternary Operator in C

C Code:

int condition = 1; // true
int number = condition ? 10 : 20;
printf("The number is: %d\n", number);

7.1.5 Type Consistency in if Expressions

All branches of an if expression must return values of the same type.

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        "six" // Error: mismatched types
    };
}

Error:

error[E0308]: if and else have incompatible types

Explanation: The if branch returns an i32, but the else branch returns a &str. Rust's type system enforces consistency to prevent runtime errors.

7.1.6 The match Statement

Rust's match statement is a powerful control flow construct for pattern matching. It is more versatile than C's switch statement.

fn main() {
    let number = 2;
    match number {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Other"),
    }
}

Key Points:

  • Patterns: match can handle a wide range of patterns.
  • Exhaustiveness Checking: The compiler ensures all possible cases are covered.
  • Wildcard Pattern _: Acts as a catch-all, similar to default in C.

Comparison with C's switch

C Code:

int number = 2;
switch (number) {
    case 1:
        printf("One\n");
        break;
    case 2:
        printf("Two\n");
        break;
    default:
        printf("Other\n");
        break;
}

Advantages of Rust's match:

  • No Fall-Through: Each arm is independent; there's no implicit fall-through.
  • Pattern Matching: Can match on more complex patterns, including ranges and destructured data.

We will explore Rust's powerful pattern matching and the match statement in full detail in a later chapter.