Phase 1Getting started

#4 Functions and control flow

fn, if, loop, match

Defining functions

Functions are declared with the fn keyword. Rust uses snake_case for function names by convention:

A simple function
fn greet(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    greet("Alice");
}

Functions can be defined anywhere in the file — before or after main. Rust does not require forward declarations.

Parameters and return types

Every function parameter must have its type annotated. To return a value, declare the return type with ->:

Function with return type
fn add(a: i32, b: i32) -> i32 {
    a + b  // No semicolon = this is the return value
}

fn main() {
    let result = add(3, 7);
    println!("The sum of 3 and 7 is: {result}");
}

Notice there is no semicolon on the last line of add. In Rust, the last expression in a function is implicitly returned. You can also use the return keyword for early returns.

if / else

Conditional logic works with if, else if, and else. The condition must be a bool — Rust does not automatically convert numbers to booleans:

if / else
let x = 5;

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

Since if is an expression in Rust, you can use it to assign a value:

if as an expression
let condition = true;
let number = if condition { 5 } else { 6 };
// number is 5

Loops

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

loop — infinite loop
let mut counter = 0;

loop {
    counter += 1;
    if counter == 3 {
        break;  // Exit the loop
    }
}
// counter is 3
while — conditional loop
let mut count = 1;

while count <= 3 {
    println!("Count: {count}");
    count += 1;
}
for — iterate over a range or collection
// Range
for i in 1..=3 {
    println!("Count: {i}");
}

// Collection
let fruits = ["apple", "banana", "cherry"];
for fruit in fruits {
    println!("Fruit: {fruit}");
}

The for loop is the most common in Rust. The range 1..=3 includes 3 (inclusive), while 1..3 would exclude it.

The match expression

match is Rust's powerful pattern matching construct. It's like a switch statement but much more powerful:

match expression
let number = 1;

match number {
    1 => println!("One"),
    2 => println!("Two"),
    3 => println!("Three"),
    other => println!("Other number: {other}"),
}

The match expression must be exhaustive — it must cover every possible value. The underscore _ or a variable name (like other) acts as a catch-all pattern.

Your turn

Try running the functions demo with cargo run and cargo check in the terminal below:

terminal — cargo
user@stemlegacy:~/functions-demo$