Skip to content
Functions
step 1/5

Reading — step 1 of 5

Learn

~2 min readFunctions and Ownership

Functions

Rust functions look like functions everywhere — until you notice what's missing at the end of them. That missing semicolon is a real language feature, it's idiomatic everywhere, and misunderstanding it produces Rust's most-Googled beginner error. Five minutes here saves hours later.

The shape

fn add(a: i32, b: i32) -> i32 {
    a + b
}
  • Parameter types are required — no inference in signatures, by design: a function's signature is its contract, and contracts are spelled out.
  • The return type follows ->.
  • Naming: snake_case, enforced by compiler warnings.

Expressions vs statements: the semicolon rule

The body above ends with a + bno semicolon — and that's the return. The rule:

  • An expression without a semicolon at the end of a block is the block's value.
  • Adding a semicolon turns an expression into a statement that evaluates to () — Rust's "nothing" type.

So this fails, instructively:

fn add(a: i32, b: i32) -> i32 {
    a + b;      // ← semicolon!
}
// error[E0308]: mismatched types — expected `i32`, found `()`
// help: remove this semicolon

Read that error once, carefully, and you'll recognize it forever: "expected i32, found ()" almost always means a stray semicolon on the last line. The compiler even points at it.

return exists — for early exits:

fn classify(n: i32) -> &'static str {
    if n < 0 {
        return "negative";     // bail early: use return
    }
    if n == 0 { "zero" } else { "positive" }   // final value: no semicolon
}

Idiomatic Rust reserves return for early exit and lets the final expression fall out the bottom. Since if and match are expressions, whole functions are often a single expression — that classify has no return on its main path and no temporary variable, and that's the house style.

Values in, value out

Arguments pass by value — for simple Copy types like integers, that's a plain copy; mutating a parameter inside doesn't touch the caller's variable. (For String and Vec, passing by value moves ownership — the subject of the next chapter's big reveal; today's exercises stick to integers precisely so you learn the shape first.)

No return type means the function returns ():

fn greet(name: &str) {           // returns () implicitly
    println!("Hi, {name}");
}

Your exercise: Square It

Define fn square(n: i64) -> i64, read a number in main, print square(n).

fn square(n: i64) -> i64 {
    n * n                        // expression return — no semicolon, no `return`
}

fn main() {
    let n: i64 = read_input();   // your stdin pattern from earlier lessons
    println!("{}", square(n));
}

Keep the discipline from the Go track if you've been there — and it's doubly idiomatic here: logic in functions (pure, testable), I/O in main (glue). Rust's expression orientation makes the logic functions satisfyingly terse; let them be terse, and let main do the plumbing.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…