rust programming

Control Flow in Rust

Introduction

Control flow is how we direct the execution path of our program based on conditions and repetition. Rust provides several control flow constructs that are both powerful and safe.

Conditional Statements (if/else)

Basic if/else Syntax

fn main() {
    let number = 3;
    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

> Important: Unlike many other languages, Rust has some distinct rules for conditionals: > 1. Parentheses around conditions are optional > 2. Curly braces {} are mandatory, even for single-line blocks > 3. Conditions must be boolean expressions

Multiple Conditions (else if)

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

If Expressions

Rust's if is an expression, meaning it can return values:

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

> Note: Both branches must return the same type!

Loops

Rust provides three types of loops: loop, while, and for.

1. loop - Infinite Loops

The loop keyword creates an infinite loop that can be broken out of using break:

fn main() {
    let mut counter = 0;
    
    let result = loop {
        counter += 1;
        
        if counter == 10 {
            break counter * 2;  // Returns a value!
        }
    };
    
    println!("The result is {}", result);  // Prints: The result is 20
}

Key Features:

  • Can run indefinitely
  • Can break with a return value
  • Useful for retry operations
  • Good for cases where the exit condition is in the middle of the loop

2. while Loops

While loops continue as long as a condition is true:

fn main() {
    let mut number = 3;
    
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    
    println!("LIFTOFF!");
}

Common Use Cases:

  • Conditional iteration
  • Reading input until a condition is met
  • Processing data with an unknown size

3. for Loops

For loops are used to iterate over collections:

// Iterating over an array
fn main() {
    let numbers = [10, 20, 30, 40, 50];
    
    // Using iterator
    for number in numbers.iter() {
        println!("Value: {}", number);
    }
    
    // Using range
    for i in 0..numbers.len() {
        println!("numbers[{}] = {}", i, numbers[i]);
    }
}

Key Features:

  • Safe and efficient
  • No index out of bounds errors
  • Works with any iterable collection
  • Can use ranges with .. or ..=

Advanced Control Flow Patterns

1. Breaking Outer Loops

You can break outer loops using labels:

'outer: loop {
    loop {
        if condition {
            break 'outer;  // Breaks the outer loop
        }
    }
}

2. Continuing Loops

The continue keyword skips to the next iteration:

fn main() {
    for number in 1..=5 {
        if number % 2 == 0 {
            continue;  // Skip even numbers
        }
        println!("{}", number);
    }
}

3. Conditional Compilation

#[cfg(target_os = "linux")]
fn linux_only() {
    println!("This only runs on Linux!");
}

Best Practices

  1. Choose the Right Loop

    • Use for when iterating over collections
    • Use while when you need a condition
    • Use loop when you need to retry operations
  2. Loop Safety

    • Always ensure loops can terminate
    • Use break conditions
    • Consider using timeouts for potentially infinite loops
  3. Performance Considerations

    • Prefer for over while when possible
    • Use iterators instead of index-based access
    • Avoid unnecessary bounds checking

Common Pitfalls

  1. Infinite Loops
// Wrong
while true {  // Use loop instead
    // ...
}

// Correct
loop {
    // ...
}
  1. Type Mismatch in If Expressions
// Wrong
let number = if condition { 5 } else { "six" };  // Error!

// Correct
let number = if condition { 5 } else { 6 };
  1. Forgetting Break Conditions
// Wrong
loop {
    do_something();
    // Oops, no break condition!
}

// Correct
loop {
    do_something();
    if should_stop() {
        break;
    }
}

Next Steps

After mastering control flow:

  • Learn about pattern matching with match
  • Explore error handling with Result and Option
  • Study iterators and closures
  • Understand async control flow

Remember: Rust's control flow constructs are designed to be safe and prevent common programming errors. Understanding them well is crucial for writing reliable Rust programs.