Control Flow

Conditional

Condition is expected to be a bool, other values are not automatically converted to a Boolean.

fn main() {
    let x = 42;
    if x == 0 {
        println!("zero!");
    } else if x < 100 {
        println!("small");
    } else {
        println!("large");
    }
}

We can use if as an expression, which returns the value of the last expression in the block (notice missing ;, for returning the value).

We can use if in an expression

fn main() {
    let x = 10;
    let size = if x < 20 { "small" } else { "large" }; 
    println!("number size: {}", size);
}

Note that values in both arm should have same data type since it has to be evaluated and assigned to the size variable at runtime.

Match Case

Match case allows to compare a value against multiple patterns and execute code based on the first matching pattern. Patterns can be made up of

  • literal values,
  • destructured arrays, enums, structs or tuples
  • variables
  • wildcards
  • Placeholders
#![allow(unused)]
fn main() {
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}
}
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),  //
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {state:?}!");
            25
        }
    }
}

fn main() {
    println!("penny: {}", value_in_cents(Coin::Quarter(UsState::Alaska)));
}

Match with Option

#![allow(unused)]
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

Unlike if let, else if, else if letand else , match in Rust needs to be exhaustive in the sense that all possibilities for the value in the match expression must be accounted for. One wayy to ensure you've covered every possiblity is to have a catchall pattern for the last arm using a variable name matching any value, or using _ as a placeholder that doesn't binds to a variable ignoring any other value without any warning.

    let dice_roll = 9;
    match dice_roll {
        1..=3 => add_fancy_hat(),  // match range of patterns
        5 | 7 => remove_fancy_hat(), // match multiple patterns
        other => move_player(other), // catch all other cases in variable `other`
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}

if let

if let is an expression that lets you combine if and let into a single construct to match one pattern while ignoring the rest. This means less boilerplate code and works the same way without the exhaustive checking of match.

#![allow(unused)]

fn main() {
let config_max = Some(3u8);

match config_max {
    Some(max) => println!("The maximum is configured to be {max}"),
    _ => (),  // ignore everything else i.e `None`
}

// alternatively with if let
if let Some(max) = config_max {
    println!("The maximum is configured to be {max}");
}
}

with else

#![allow(unused)]

fn main() {
let mut count = 0;
match coin {
    Coin::Quarter(state) => println!("State quarter from {state:?}!"),
    _ => count += 1,
}


let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {state:?}!");
} else {
    count += 1;
}
}

Loops

While

While loop is a conditional loop that runs till the condition is satisfied.

fn main() {
    let mut x = 200;
    while x >= 10 {
        x = x / 2;
    }
    println!("Final x: {x}");
}

For

For loop, for-in loop is an iterator loop that iter over an elements of an iterator which can be fixed or could theoretically go on indefinitely.


fn main() {
    for x in 1..5 {
        println!("x: {x}");
    }

    for x in 1..=5 {
        println!("x: {x}");
    }
    for elem in [1, 2, 3, 4, 5] {
        println!("elem: {elem}");
    }

    for (index, elem) in [1, 2, 3, 4, 5].iter().enumerate() {
        println!("index: {index}, elem: {elem}");
    }

    for i in std::iter::repeat(5) {
    println!("turns out {i} never stops being 5");
    break; // would loop forever otherwise
}
}

Under the hood for loops use a concept called "iterators" to handle iterating over different kinds of ranges/collections.

Loop

Loop is idiomatic infinite loop similar to while true.

fn main() {

}

break and continue

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        if i > 5 {
            break;
        }
        if i % 2 == 0 {
            continue;
        }
        println!("{}", i);
    }
}

Both continue and break can optionally take a label argument which is used to break out of nested loops, can be used for loop, while, for.

fn main() {
    let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]];
    let mut elements_searched = 0;
    let target_value = 10;
    'outer: for i in 0..=2 {
        for j in 0..=2 {
            elements_searched += 1;
            if s[i][j] == target_value {
                break 'outer;
            }
        }
    }
    print!("elements searched: {elements_searched}");
}

Example: Fibonacci Number


fn fib(n: u32) -> u32 {
    if n < 2 {
        todo!("Implement this");
    } else {
        todo!("Implement this");
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}
Solution

fn fib(n: u32) -> u32 {
    if n < 2 {
        return n;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}