Skip to content
Educora
Beginner15 min3 / 10

Functions, expressions and control flow

Write functions, understand the difference between expressions and statements, use `if` as an expression that returns a value, and apply `loop`, `while` and `for` together with ranges.

Check yourself
In this lesson you will learn
  • Write functions with parameters and return values
  • Tell expressions from statements and explain the role of the semicolon
  • Use if as an expression
  • Write loop, while and for loops with ranges

A tea recipe is made of named steps such as “boil the water”, “warm the teapot” and “let the tea brew”. Some steps depend on a condition (“if the water is boiling…”), and others repeat (“pour into every glass”). Programs are built the same way: named steps are functions, decisions are **if, and repetition is done with loops**.

Functions

A function is declared with the fn keyword, and its name is written in snake_case. The type of every parameter must be given. If the function returns a value, its type is written after the -> arrow. The order of functions in the file does not matter: main can call a function declared below it.

Rust
fn main() {
    greet("Leyla");
    let a = area(3, 4);
    println!("Area: {a}");
}

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

fn area(width: u32, height: u32) -> u32 {
    width * height
}
Expected output
Hello, Leyla!
Area: 12
&str is a text type (we will study it in detail in lesson 4). There is no return in area: the last expression, width * height, is returned automatically.

Expressions and statements

Definition
Expression and statement

An expression is code that is evaluated and produces a value: 5 + 3, area(2, 3), a { } block, an if. A statement performs an action but produces no value: let x = 5; or any line ending in ;. Putting ; after an expression turns it into a statement.

Rust
fn main() {
    let y = {
        let x = 3;
        x * x + 1
    };
    println!("y = {y}");
}
Expected output
y = 10
A block's value is its last expression: the line x * x + 1 has no ; at the end.
Error E0308
fn square(x: i32) -> i32 {
    x * x;
}
Correct
fn square(x: i32) -> i32 {
    x * x
}
On the left, the ; turned the expression into a statement, so the function returns nothing — the empty type (). The compiler expected i32 and suggests removing the semicolon.
Text
error[E0308]: mismatched types
 --> src/main.rs:1:22
  |
1 | fn square(x: i32) -> i32 {
  |    ------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     x * x;
  |          - help: remove this semicolon to return this value

if is an expression

In Rust, an if condition needs no parentheses, but the curly braces are required. The condition must be of type bool. if is also an expression: you can store its result in a variable or return it from a function. For that, all branches must produce values of the same type.

Rust
fn grade(score: u32) -> char {
    if score >= 90 {
        'A'
    } else if score >= 70 {
        'B'
    } else {
        'C'
    }
}

fn main() {
    let temperature = 28;
    let clothes = if temperature > 25 { "T-shirt" } else { "jacket" };
    println!("Wear a {clothes}");
    println!("95 -> {}", grade(95));
    println!("72 -> {}", grade(72));
    println!("40 -> {}", grade(40));
}
Expected output
Wear a T-shirt
95 -> A
72 -> B
40 -> C

Loops: loop, while, for

Rust has three loops. loop repeats forever until it is stopped with break, and break can also return a value. while runs as long as a condition is true. for walks through the elements of a collection or a range one by one — it is the most used and the safest loop.

Rust
fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 10;
        }
    };
    println!("result = {result}");

    let mut n = 3;
    while n > 0 {
        println!("{n}...");
        n -= 1;
    }
    println!("Liftoff!");
}
Expected output
result = 50
3...
2...
1...
Liftoff!
Rust
fn main() {
    for i in 1..4 {
        print!("{i} ");
    }
    println!();

    for i in (1..=3).rev() {
        print!("{i} ");
    }
    println!();

    let cities = ["Baku", "Ganja", "Shaki"];
    for (index, city) in cities.iter().enumerate() {
        println!("{index}: {city}");
    }

    let total: i32 = (1..=100).sum();
    println!("1 + 2 + ... + 100 = {total}");
}
Expected output
1 2 3 
3 2 1 
0: Baku
1: Ganja
2: Shaki
1 + 2 + ... + 100 = 5050
print! prints without a line break, and an empty println!() moves to a new line. enumerate() pairs each element with its index.
SyntaxValuesNote
1..41, 2, 3end excluded
1..=41, 2, 3, 4end included
(1..4).rev()3, 2, 1reversed
(0..10).step_by(3)0, 3, 6, 9with a step

In nested loops, break stops only the innermost loop. To stop the outer loop, give it a label starting with an apostrophe and write break 'label:

Rust
fn main() {
    'outer: for row in 1..=3 {
        for col in 1..=3 {
            if row * col == 4 {
                println!("found at ({row}, {col})");
                break 'outer;
            }
        }
    }
    println!("search finished");
}
Expected output
found at (2, 2)
search finished

Key points

  • Functions are declared with fn; parameter types are required, and the return type follows ->.
  • An expression produces a value, a statement does not; a trailing ; turns an expression into a statement.
  • The last expression without ; is the value of a function or block.
  • if is an expression, and its condition must be a bool.
  • loop can return a value with break; for works with ranges: 1..4 excludes the end, 1..=4 includes it.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Which function correctly returns the square of a number?