Bitterless Rust

Functions and Control Flow

Functions

Define functions with fn. Arguments and return types need type annotations:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let result = add(1, 2);
    println!("{}", result); // 3
}

Did you notice — there's no return in add. In Rust, the last expression becomes the return value. Be careful: adding a semicolon ; turns it into a "statement" and it stops returning a value:

fn add(a: i32, b: i32) -> i32 {
    a + b   // ← no semicolon = returns this value
}

fn add_wrong(a: i32, b: i32) -> i32 {
    a + b;  // ← semicolon = doesn't return a value = compile error
}

You can also use return to return explicitly:

fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

Use return when you want to exit early. When the last expression can do the job, writing it without a semicolon is the Rust way.

Functions That Return Nothing

Functions that don't return anything omit the ->:

fn greet(name: String) {
    println!("Hello, {}!", name);
}

if / else

fn main() {
    let x = 42;

    if x > 0 {
        println!("positive");
    } else if x < 0 {
        println!("negative");
    } else {
        println!("zero");
    }
}

No () needed around the condition. The compiler will tell you they're unnecessary if you add them.

if Is an Expression

Rust's if is an expression, so it can return a value:

fn main() {
    let x = 42;
    let label = if x > 0 { "positive" } else { "negative" };
    println!("{}", label);
}

Use it instead of the ternary operator (? :).

match

match is pattern matching. A powered-up version of switch from other languages:

fn main() {
    let x = 2;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("other"),  // _ matches everything else
    }
}

_ is the default case (switch's default).

match is also an expression, so it can return a value:

fn main() {
    let x = 2;

    let name = match x {
        1 => "one",
        2 => "two",
        _ => "other",
    };

    println!("{}", name); // two
}

match really shines when combined with enums in later chapters.

for Loop

fn main() {
    // 0 to 4
    for i in 0..5 {
        println!("{}", i);
    }

    // Looping over an array (Vec)
    let names = vec!["Alice", "Bob", "Charlie"];
    for name in names {
        println!("Hello, {}!", name);
    }
}

0..5 is a range meaning "0 or more, less than 5." 0..=5 means "0 or more, 5 or less."

vec![...] is a macro that creates a Vec (dynamic array). More on this in a later chapter.

while Loop

fn main() {
    let mut count = 0;

    while count < 5 {
        println!("{}", count);
        count += 1;
    }
}

loop (Infinite Loop)

fn main() {
    let mut count = 0;

    loop {
        if count >= 5 {
            break;
        }
        println!("{}", count);
        count += 1;
    }
}

Use break to exit. loop can also be used as an expression:

fn main() {
    let mut count = 0;

    let final_count = loop {
        count += 1;
        if count >= 10 {
            break count;  // passing a value to break makes it the loop's return value
        }
    };

    println!("{}", final_count); // 10
}

Functions and control flow are done. Next, let's learn structs and enums to create our own data types.

Back to book