</>
Skip to content
Rust lessons (17/43)

Rust — Functions

Function anatomy

fn add(a: i32, b: i32) -> i32 {
    a + b
}
PieceRule
fnkeyword that starts every function
addsnake_case by convention (enforced by compiler warnings)
a: i32parameter — type is mandatory, always
-> i32return type after the arrow

Rust never infers function parameter or return types — signatures are contracts, checked everywhere the function is called.

Return via final expression (no return needed)

The last expression in the body IS the return value:

fn square(x: i32) -> i32 {
    x * x          // no semicolon!
}

Semicolon = statement = returns () (nothing). This is Rust's most common beginner error:

fn broken(x: i32) -> i32 {
    x * x;         // ❌ semicolon discards the value
}

Explicit return — for early exits

fn classify(n: i32) -> &'static str {
    if n < 0 {
        return "negative";     // early return uses `return`
    }
    if n == 0 {
        return "zero";
    }
    "positive"                 // happy path stays implicit
}

Convention: use return only for early exits; let the final line be the value.

Unit type — "returns nothing"

Functions without a return type implicitly return (), Rust's empty tuple:

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

Multiple parameters & defaults don't exist

fn volume(w: f64, h: f64, d: f64) -> f64 {
    w * h * d
}

No default parameter values in Rust — the pattern instead:

fn connect(timeout_secs: Option<u64>) { … }

connect(None);              // caller decides explicitly
connect(Some(30));

Statements inside are fine

fn analyze(nums: &[i32]) -> (i32, f64) {
    let sum: i32 = nums.iter().sum();
    let count = nums.len();

    let avg = if count == 0 {
        0.0
    } else {
        sum as f64 / count as f64
    };

    (sum, avg)               // returning a tuple!
}

let (total, average) = analyze(&[90, 80, 70]);

Tuples let one function hand back several related values.

Calling functions

fn main() {
    let result = add(2, 3);       // arguments match declared types exactly
    println!("2+3 = {}", result);

    // nested calls compose naturally:
    println!("{}", add(square(2), square(3)));   // 13
}

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

Order of definition doesn't matter — no forward declarations needed.

Gotchas: missing self-style confusion comes later with methods · snake_case violations produce warnings · returning a reference needs lifetime talk (later lesson) · integer overflow in pure functions panics in debug.

Mini Practice

  1. celsius_to_fahrenheit(f64) -> f64; test 100 → 212.
  2. max_of_three(i32,i32,i32) using two ifs.
  3. Return (min, max) tuple from a slice.
  4. Early-return validator for username length 3–16.
  5. Break square() with a semicolon; internalize the error.

Next: scope →

Related Topics

Frequently Asked Questions about Functions

What is Functions in Rust?

Functions is a fundamental concept in Rust. This lesson explains it step by step with clear examples, making it easy for beginners to understand.

How do I learn Functions?

Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Functions.

Why is Functions important in Rust?

Functions is essential for Rust development. Understanding this concept will help you write better code and solve real-world problems more effectively.