Rust - Expressions


Rust is an expression-based language. Unlike statements:

  • Expressions do not end in a semi-colon
  • Expressions evaluate to a resulting value (returns something)

For example:

fn main() {
    let x = 1;

    // The {} block is an expression. The entire line is a statement.
    let y = {
        let x = 7; // Different x
        x + 1 // Notice how there is no semi-colon here.
    };

    println!("y = {}", y);
}

For

For loops are used to iterate over collections. Don't worry, a normal C++ for loop is still applicable. You just need to make it more like Python and iterate through some numbers!

fn main() {
    let arr = [1, 2, 3, 4, 5];

    // Iterate through a collection
    for element in arr.iter() {
        println!("The value is: {}", element);
    }

    // Iterate through a range of values
    for number in 1..6 {
        println!("The value is: {}", number);
    }

    // Iterate through a range, backwards
    for number in (1..6).rev() {
        println!("The value is: {}", number);
    }

    // Inclusive end
    for _ in 1..=9 {}

    // Use vars
    let a = 0;
    let b = 10;
    for i in a..b {
        println!("{}", i)  # println! requires a string literal, not just a variable
    }
}

If Expression

Rust is a standard if, else if, else language, however, only boolean values are allowed for the conditional. Integers will cause a mismatched types error.

fn main() {
    let number = 12;

    // Standard if, else if, else expression
    if number % 3 == 0 {
        println!("Number is divisible by 3");
    } else if number % 2 == 0 {
        println!("Number is divisible by 2");
    } else {
        println!("Math hard, I gave up.");
    }

    // Using "if" in a let statement (because it's an expression, not a statement)
    let number = if number > 0 { number } else { 0 }; // Return types must match
    println!("number is now: {}", number);
}

If there are many else if expressions, Rust recommends using match instead.

If Let

The if let is the little sibling to match. if let allows you to match only one arm of a match expression. The if let expression takes a pattern on the left hand side of an EQUAL SIGN and an expression on the right hand side of an EQUAL SIGN.

if let can also be combined with normal if-else statements.

Example:

#[derive(Debug)]
enum Breed {
    Husky,
    Poodle,
    Lab,
    // Etc.
}

enum Animal {
    Cat,
    Giraffe,
    Dog(Breed), // This Animal::Dog type contains Breed data
}

fn main() {
    // Example A
    let some_i32 = Some(17i32); // Make 17 an i32 type
    if let Some(3) = some_i32 { // Notice the "=" sign
        println!("Three!");
    } else {
        println!("The number was not three :(");
    }

    // Example B
    let dog = Animal::Dog(Breed::Husky);
    if let Animal::Dog(breed) = dog {
        println!("We found a {:?} dog!", breed);
    }
}

Loop

Rust has three kinds of loops: loop, while, and for

There also exists the break statement to break out of a loop and the continue statement to immediately go to the next iteration of the loop.

Values can be returned from loops. For example:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 5 {
            break counter * 7;
        }
    };

    println!("result = {}", result);
}
Loop

The loop loop creates an infinite loop.

fn main() {
    loop {
        println!("Never ending!");
    }
}

Match

match is like a switch-case in most other languages. It consists of the match keyword followed by an expression (generally a single variable). In the curly brackets comes the arms. Each arm consists of a pattern and some code.

Matches are exhaustive. Every case for an enum must be handled. match does provide a catch all via the _ placeholder.

A | can be used to match multiple patterns.

Each arm of a match expression must return the same type (the "never type" is an exception).

Example:

#[derive(Debug)]
enum Breed {
    Husky,
    Poodle,
    Lab,
    // Etc.
}

enum Animal {
    Cat,
    Giraffe,
    Dog(Breed), // This Animal::Dog type contains Breed data
    Fox,
    Hamster,
    Snake,
    Daltie,
}

fn sound(animal: Animal) -> String {
    match animal {
        Animal::Cat => String::from("Meow"),
        Animal::Giraffe => String::from("Hello good Sir"),
        Animal::Dog(breed) => { // Function like arm block
            println!("I am a {:?}", breed);
            String::from("Woof")
        },
        Animal::Snake | Animal::Daltie => String::from("I'm a sssnake!"),
        _ => String::from("??"), // Catches the Fox and Hamster cases
    }
}

fn main() {
    let husky = Animal::Dog(Breed::Husky);
    println!("{}", sound(husky));

    // Match range
    let x = 2;
    
    match x {
        1..=7 => println!("Small num"),
        _ => println!("Invalid"),
    }
}
Destructing

Structs, enums, tuples, and references can be destructed to their individual parts.

struct Point{ x: u32, y: u32 }

fn main() {
    let p = Point { x: 0, y: 7 };
    let Point{ x: a, y } = p; // a and y are now valid variables in this scope

    // Match destructured values
    match p {
        Point { x, y: 0 } => println!("On the x-axis at {}", x),
        Point { x: 0, y } => println!("On the y-axis at {}", y),
        Point { x, y } => println!("Somewhere, over a rainbow? ({}, {})", x, y),
    }
}
Ignoring Parts
struct Point{ x: u32, y: u32 , z: u32}

fn main() {
    let p = Point { x: 0, y: 7, z: 14 };

    // Ignore everything other than y
    match p {
        Point { y, .. } => println!("Only care about y: {}", y), // 7
    }

    let numbers = (1, 2, 3, 4, 5);

    // Only match first and last values
    match numbers {
        (first, .., last) => println!("{}, {}", first, last), // 1, 5
    }
}
Match Guards
fn main() {
    let num = Some(7);

    match num {
        Some(x) if x < 5 => println!("Less than 5"),
        // Match expressions much be exhaustive, so not including
        // this line would cause an complication error
        Some(x) => println!("{}", x),
        None => (),
    }
}
@

@ allows you to both test a pattern and bind a variable.

enum Height {
    Meters { m: u32 },
}

fn main() {
    let tall = Height::Meters{ m: 7 };

    match tall {
        Height::Meters {
            m: meters_var @ 0..=9,
        } => println!("You're only {} meters tall! You short!", meters_var),
        Height::Meters { m: 10..=99 } => {
            println!("You fit") // variable m is not available, need @ if want variable binding
        }
        Height::Meters { m } => println!("{} is so tall", m), // Since there is no testing, m is available
    }
}

While

Conditional looping!

fn main() {
    let mut number = 0;

    while number != 5 {
        println!("{}", number);

        number += 1;
    }
}

While Let

Similar to if let, while let allows you to loop until the "if let" is false.

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop {
    println!("{}", top);
}