Rust - If Expressions

Overview

Estimated time: 30–40 minutes

Master conditional logic in Rust using if expressions. Learn the difference between expressions and statements, multiple conditions with else if, pattern matching with if let, and how to use if expressions for assignment and return values.

Learning Objectives

Prerequisites

If Expressions vs Statements

In Rust, if is an expression, not a statement. This means it returns a value that can be used in assignments and other contexts.

Basic If Expression

fn main() {
    let number = 5;
    
    if number > 0 {
        println!("The number is positive");
    }
    
    let is_even = if number % 2 == 0 {
        "even"
    } else {
        "odd"
    };
    
    println!("The number {} is {}", number, is_even);
}

Expected Output:

The number is positive
The number 5 is odd

If Expression Anatomy

fn main() {
    let condition = true;
    
    // If expression with explicit return values
    let result = if condition {
        "condition was true"    // No semicolon - this is the return value
    } else {
        "condition was false"   // No semicolon - this is the return value
    };
    
    println!("Result: {}", result);
    
    // If expression returning numbers
    let number = if condition { 1 } else { 0 };
    println!("Number: {}", number);
}

Expected Output:

Result: condition was true
Number: 1

Basic If, Else If, Else

Simple If-Else

fn main() {
    let temperature = 25;
    
    if temperature > 30 {
        println!("It's hot outside!");
    } else {
        println!("It's not that hot.");
    }
    
    // Using the expression result
    let clothing = if temperature > 20 {
        "t-shirt"
    } else {
        "jacket"
    };
    
    println!("You should wear a {}", clothing);
}

Expected Output:

It's not that hot.
You should wear a t-shirt

Multiple Conditions with Else If

fn main() {
    let score = 85;
    
    let grade = if score >= 90 {
        'A'
    } else if score >= 80 {
        'B'
    } else if score >= 70 {
        'C'
    } else if score >= 60 {
        'D'
    } else {
        'F'
    };
    
    println!("Score: {}, Grade: {}", score, grade);
    
    // More complex example
    let age = 25;
    let has_license = true;
    let has_car = false;
    
    if age >= 18 && has_license {
        if has_car {
            println!("You can drive your own car!");
        } else {
            println!("You can drive, but you need to rent or borrow a car.");
        }
    } else if age >= 16 {
        println!("You can get a learner's permit.");
    } else {
        println!("You're too young to drive.");
    }
}

Expected Output:

Score: 85, Grade: B
You can drive, but you need to rent or borrow a car.

Logical Operators in Conditions

AND, OR, NOT Operators

fn main() {
    let age = 20;
    let has_id = true;
    let is_member = false;
    
    // AND operator (&&)
    if age >= 18 && has_id {
        println!("You can enter the venue");
    }
    
    // OR operator (||)
    if age >= 21 || is_member {
        println!("You can get the discount");
    } else {
        println!("No discount available");
    }
    
    // NOT operator (!)
    if !is_member {
        println!("Consider becoming a member for exclusive benefits");
    }
    
    // Complex conditions
    let username = "admin";
    let password = "secret123";
    let is_authenticated = false;
    
    if (username == "admin" && password == "secret123") || is_authenticated {
        println!("Access granted");
    } else {
        println!("Access denied");
    }
}

Expected Output:

You can enter the venue
No discount available
Consider becoming a member for exclusive benefits
Access granted

Short-Circuit Evaluation

fn main() {
    let x = 5;
    let y = 0;
    
    // AND short-circuits: if first is false, second isn't evaluated
    if y != 0 && x / y > 2 {  // y != 0 is false, so x / y is never evaluated
        println!("This won't print");
    } else {
        println!("Short-circuit prevented division by zero");
    }
    
    // OR short-circuits: if first is true, second isn't evaluated
    let result = true || expensive_computation();
    println!("Result: {}", result);
}

fn expensive_computation() -> bool {
    println!("This expensive computation won't run");
    false
}

Expected Output:

Short-circuit prevented division by zero
Result: true

If Let Pattern Matching

Basic If Let

if let allows you to combine pattern matching with conditional logic:

fn main() {
    let some_number = Some(42);
    let no_number: Option = None;
    
    // Traditional match approach
    match some_number {
        Some(value) => println!("Got a value: {}", value),
        None => println!("Got nothing"),
    }
    
    // if let approach (more concise for single pattern)
    if let Some(value) = some_number {
        println!("if let: Got a value: {}", value);
    } else {
        println!("if let: Got nothing");
    }
    
    // Another example with None
    if let Some(value) = no_number {
        println!("This won't print: {}", value);
    } else {
        println!("no_number was None");
    }
}

Expected Output:

Got a value: 42
if let: Got a value: 42
no_number was None

If Let with Different Types

fn main() {
    let message = Ok("Success!");
    let error_message: Result<&str, &str> = Err("Something went wrong");
    
    // if let with Result
    if let Ok(msg) = message {
        println!("Success message: {}", msg);
    } else {
        println!("Got an error");
    }
    
    if let Err(error) = error_message {
        println!("Error occurred: {}", error);
    } else {
        println!("Operation succeeded");
    }
    
    // if let with tuples
    let pair = (3, 5);
    if let (x, y) = pair {
        println!("Pair values: x={}, y={}", x, y);
    }
    
    // if let with custom enums
    enum Color {
        Red,
        Green,
        Blue,
        Rgb(u8, u8, u8),
    }
    
    let color = Color::Rgb(255, 0, 128);
    
    if let Color::Rgb(r, g, b) = color {
        println!("RGB color: red={}, green={}, blue={}", r, g, b);
    } else {
        println!("Not an RGB color");
    }
}

Expected Output:

Success message: Success!
Error occurred: Something went wrong
Pair values: x=3, y=5
RGB color: red=255, green=0, blue=128

Using If in Different Contexts

If in Variable Assignment

fn main() {
    let weather = "sunny";
    
    // Assigning result of if expression
    let activity = if weather == "sunny" {
        "go to the beach"
    } else if weather == "rainy" {
        "stay inside and read"
    } else {
        "go for a walk"
    };
    
    println!("Since it's {}, let's {}", weather, activity);
    
    // More complex assignment
    let x = 10;
    let y = 20;
    
    let max = if x > y { x } else { y };
    let relationship = if x == y {
        "equal"
    } else if x > y {
        "x is greater"
    } else {
        "y is greater"
    };
    
    println!("x: {}, y: {}, max: {}, relationship: {}", x, y, max, relationship);
}

Expected Output:

Since it's sunny, let's go to the beach
x: 10, y: 20, max: 20, relationship: y is greater

If in Function Returns

fn main() {
    let temperature = 25;
    let humidity = 60;
    
    let comfort_level = assess_comfort(temperature, humidity);
    println!("Comfort level: {}", comfort_level);
    
    let number = -5;
    let abs_value = absolute_value(number);
    println!("Absolute value of {}: {}", number, abs_value);
}

fn assess_comfort(temp: i32, humidity: i32) -> &'static str {
    if temp >= 20 && temp <= 26 && humidity >= 40 && humidity <= 60 {
        "comfortable"
    } else if temp > 30 || humidity > 70 {
        "too hot/humid"
    } else if temp < 15 || humidity < 30 {
        "too cold/dry"
    } else {
        "acceptable"
    }
}

fn absolute_value(n: i32) -> i32 {
    if n < 0 { -n } else { n }
}

Expected Output:

Comfort level: comfortable
Absolute value of -5: 5

Nested If Expressions

Simple Nesting

fn main() {
    let user_type = "premium";
    let subscription_active = true;
    let payment_overdue = false;
    
    if user_type == "premium" {
        if subscription_active {
            if !payment_overdue {
                println!("Full access granted");
            } else {
                println!("Payment required for full access");
            }
        } else {
            println!("Please renew your subscription");
        }
    } else {
        println!("Limited access - consider upgrading to premium");
    }
    
    // Same logic with combined conditions (often cleaner)
    if user_type == "premium" && subscription_active && !payment_overdue {
        println!("Alternative: Full access granted");
    } else if user_type == "premium" && subscription_active {
        println!("Alternative: Payment required for full access");
    } else if user_type == "premium" {
        println!("Alternative: Please renew your subscription");
    } else {
        println!("Alternative: Limited access - consider upgrading to premium");
    }
}

Expected Output:

Full access granted
Alternative: Full access granted

Complex Nested Logic

fn main() {
    let age = 25;
    let country = "US";
    let has_passport = true;
    let criminal_record = false;
    
    let travel_status = if age >= 18 {
        if has_passport {
            if country == "US" {
                if !criminal_record {
                    "Can travel internationally"
                } else {
                    "May face travel restrictions"
                }
            } else {
                "Check visa requirements"
            }
        } else {
            "Need to get a passport first"
        }
    } else {
        "Need parental consent for passport"
    };
    
    println!("Travel status: {}", travel_status);
}

Expected Output:

Travel status: Can travel internationally

Type Consistency in If Expressions

All Branches Must Return Same Type

fn main() {
    let condition = true;
    
    // This works - both branches return i32
    let number = if condition { 42 } else { 0 };
    println!("Number: {}", number);
    
    // This would cause a compile error:
    // let mixed = if condition { 42 } else { "hello" };  // Error: type mismatch
    
    // Correct way to handle different types
    enum Value {
        Number(i32),
        Text(String),
    }
    
    let mixed_value = if condition {
        Value::Number(42)
    } else {
        Value::Text(String::from("hello"))
    };
    
    match mixed_value {
        Value::Number(n) => println!("Got number: {}", n),
        Value::Text(s) => println!("Got text: {}", s),
    }
}

Expected Output:

Number: 42
Got number: 42

Unit Type in If Expressions

fn main() {
    let print_debug = true;
    
    // If used for side effects (returns unit type)
    if print_debug {
        println!("Debug: Starting application");
        println!("Debug: Loading configuration");
    }
    
    // Explicit unit type return
    let result = if print_debug {
        println!("This branch returns ()");
        ()  // Explicit unit type
    } else {
        println!("This branch also returns ()");
        ()  // Explicit unit type
    };
    
    println!("Result type is unit: {:?}", result);
}

Expected Output:

Debug: Starting application
Debug: Loading configuration
This branch returns ()
Result type is unit: ()

Practical Examples

Input Validation

fn main() {
    let username = "admin";
    let password = "secure123";
    let age = 25;
    
    let validation_result = validate_user(username, password, age);
    println!("{}", validation_result);
}

fn validate_user(username: &str, password: &str, age: u8) -> String {
    if username.is_empty() {
        "Username cannot be empty".to_string()
    } else if username.len() < 3 {
        "Username must be at least 3 characters".to_string()
    } else if password.len() < 8 {
        "Password must be at least 8 characters".to_string()
    } else if age < 13 {
        "Must be at least 13 years old".to_string()
    } else if age < 18 {
        "Account created with parental controls".to_string()
    } else {
        "Account created successfully".to_string()
    }
}

Expected Output:

Account created successfully

Configuration Logic

fn main() {
    let environment = "production";
    let debug_mode = false;
    let log_level = "info";
    
    configure_application(environment, debug_mode, log_level);
}

fn configure_application(env: &str, debug: bool, log_level: &str) {
    let port = if env == "production" { 80 } else { 3000 };
    
    let database_url = if env == "production" {
        "postgresql://prod-db:5432/app"
    } else if env == "staging" {
        "postgresql://staging-db:5432/app"
    } else {
        "postgresql://localhost:5432/app_dev"
    };
    
    if debug {
        println!("🔧 Debug mode enabled");
        println!("🔧 Environment: {}", env);
        println!("🔧 Port: {}", port);
        println!("🔧 Database: {}", database_url);
    }
    
    if log_level == "debug" || debug {
        println!("📝 Verbose logging enabled");
    } else if log_level == "info" {
        println!("📝 Standard logging enabled");
    } else {
        println!("📝 Minimal logging enabled");
    }
    
    println!("🚀 Application configured for {} environment on port {}", env, port);
}

Expected Output:

📝 Standard logging enabled
🚀 Application configured for production environment on port 80

Best Practices

Readability Guidelines

fn main() {
    let temperature = 25;
    let is_raining = false;
    let is_weekend = true;
    
    // Good: descriptive boolean variables
    let is_warm = temperature > 20;
    let is_nice_weather = is_warm && !is_raining;
    let can_go_outside = is_nice_weather || is_weekend;
    
    if can_go_outside {
        println!("It's a good day to go outside!");
    }
    
    // Good: clear conditional logic
    let activity = if is_nice_weather && is_weekend {
        "perfect day for a picnic"
    } else if is_nice_weather {
        "good day for a walk after work"
    } else if is_weekend {
        "indoor activities"
    } else {
        "focus on work"
    };
    
    println!("Today is a {}", activity);
}

Expected Output:

It's a good day to go outside!
Today is a perfect day for a picnic

Performance Considerations

Common Pitfalls

Checks for Understanding

  1. What's the difference between an if expression and an if statement?
  2. What happens if you try to assign an if expression where branches return different types?
  3. When should you use if let instead of match?
  4. What operators can you use in if conditions?
  5. Do all if expressions need an else branch?

Answers

  1. If expressions return values that can be used in assignments; if statements in other languages don't return values
  2. Compile error - all branches must return the same type
  3. Use if let when you only care about one pattern; use match for multiple patterns
  4. && (AND), || (OR), ! (NOT), comparison operators (==, !=, <, >, etc.)
  5. Only if you're using the if expression for assignment or return values

← PreviousNext →