Functions and expressions

Rust · Essentials · 8 min

Knowing that blocks are expressions explains Rust's missing return statements and stray-semicolon errors.

Functions are declared with fn, parameters always carry explicit types, and the return type follows ->. Type inference works inside function bodies but never across signatures, which keeps signatures readable as documentation.

Rust is expression-oriented. A block { ... } evaluates to its last expression, if that expression has no trailing semicolon. That is how functions return values without return:

fn square(x: i32) -> i32 {
    x * x
}

Adding a semicolon (x * x;) turns the expression into a statement, the block then evaluates to (), and you get a "mismatched types: expected i32, found ()" error. Recognise this one; everyone hits it.

return still exists and is used for early exits, for example a guard at the top of a function. if, match and blocks are expressions too, so you can write let label = if n > 0 { "pos" } else { "non-pos" };. Both branches must then have the same type.

A function with no -> returns (). A function that never returns, such as one that always panics or loops forever, can be declared -> ! (the never type).

Function names use snake_case. Order does not matter: you can call a function defined further down the file. Rust has no default arguments or overloading; use different names, an Option parameter, or a builder struct instead.

Example

fn area(width: f64, height: f64) -> f64 {
    width * height // no semicolon: this is the return value
}

fn classify(n: i32) -> &'static str {
    if n < 0 {
        return "negative"; // early return
    }
    let kind = if n % 2 == 0 { "even" } else { "odd" };
    kind
}

fn main() {
    let total = {
        let a = area(2.0, 3.5);
        a + 1.0 // block evaluates to 8.0
    };
    println!("{total} {} {}", classify(-3), classify(4)); // 8 negative even
}

Remember

  • Parameter and return types are always written out
  • The last expression of a block, without ;, is its value
  • return is for early exits; if and match are expressions

Watch out for

  • Ending the final line with ; and getting expected i32, found ().

Check yourself

  1. What does `fn f() -> i32 { 5; }` produce?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/ch03-03-how-functions-work.html