Rust Functions

Functions in Rust are declared with fn, require explicit type annotations on every parameter, and return the value of their final expression without needing a return keyword.

Declaring a Function

A function definition starts with fn, followed by a snake_case name, a parenthesized parameter list where every parameter has an explicit type, and an optional -> ReturnType. Unlike local variables, parameter and return types are never inferred.

A function with parameters and a return type

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

fn main() {
    let sum = add(3, 4);
    println!("3 + 4 = {}", sum);
}

Expression-Based Return Values

The last expression in a function body, written without a trailing semicolon, becomes the function's return value automatically. The explicit return keyword is still available and is typically reserved for early exits.

Mixing an early return with a tail expression

fn classify(n: i32) -> &'static str {
    if n < 0 {
        return "negative";
    }

    if n == 0 {
        "zero"
    } else {
        "positive"
    }
}

fn main() {
    println!("{}", classify(-4));
    println!("{}", classify(0));
    println!("{}", classify(9));
}
Note: Adding a semicolon after a function's final expression turns it into a statement that evaluates to (), causing a type mismatch if the signature promises something else. This is one of the most common errors newcomers hit while learning Rust.

Functions Without a Return Value

A function returning the unit type

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

fn main() {
    greet("Ferris");
}
Parameter typeWhat the function receives
i32, f64, bool, char (Copy types)a copy of the value
&str, &Ta borrowed reference; the original stays owned by the caller
String, Vec<T>, or other non-Copy Townership moves into the function unless it's passed by reference
Note: When unsure whether a function should take ownership or borrow, default to borrowing with &T — it keeps the function flexible for callers, and you can always change it later if the function truly needs to own the value.
  • Parameters and return types must always be explicitly annotated; only local variables get type inference.
  • The final expression of a function body (no trailing semicolon) becomes its return value.
  • return is only needed for early exits; idiomatic Rust relies on the implicit tail expression otherwise.
  • A function with no -> Type returns (), Rust's empty-tuple unit type.

Exercise: Rust Functions

Do Rust function parameters need explicit type annotations?