[Rust] Functions: From Declaration to Return Values

These are my notes on The Rust Programming Language, chapter 3.3 "Functions"; the examples are based on that chapter.

Functions are everywhere in Rust code. You've already seen the main function; this post covers how to write your own.

Declaring and Calling Functions

In Rust, we use the fn keyword to define a new function.

Naming Convention: Snake Case

Rust uses snake case for function and variable names. This means all letters are lowercase, and we use underscores (_) to separate words.

Example: my_cool_function, calculate_distance

The Structure

A function starts with fn, followed by its name and parentheses (). The code inside the curly brackets {} is called the function body.

Rust
fn main() {
    println!("Hello, world!");

    another_function(); // Let's call it!
}

fn another_function() {
    println!("Another function.");
}

Note: In Rust, it doesn't matter whether you define another_function before or after main. It only has to be defined somewhere in a scope the caller can see.

Parameters

Parameters are special variables that let you pass data into a function. They are part of the function’s "signature."

Rust
fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Type Annotations are a Must!

When defining parameters, you must declare the type of each one (like i32 or char). This is a deliberate choice in Rust's design. It helps the compiler give you better error messages and means you don't have to label types as often in the rest of your code.

Statements vs. Expressions

This distinction matters in Rust because it's an expression-based language.

TypeDescriptionExample
StatementInstructions that perform an action but do not return a value.let y = 6;
ExpressionCode that evaluates to a resultant value.5 + 6, x + 1

Why does this matter?

In some languages, you can write x = y = 6. In Rust, you can't, because let y = 6 is a statement and doesn't return anything for x to hold.

However, a block of code inside {} can be an expression:

Rust
let y = {
    let x = 3;
    x + 1 // No semicolon here!
};
// y becomes 4

Notice the missing semicolon on the x + 1 line. In Rust, adding a semicolon turns an expression into a statement, which means it won't return a value.

Functions with Return Values

Functions can send data back to the caller. We declare the return type after an arrow ->.

Rust
fn five() -> i32 {
    5 // This is an expression that returns 5
}

fn main() {
    let x = five();
    println!("The value of x is: {x}"); // Prints 5
}

The "Implicit" Return

In Rust, the return value of a function is usually the value of the final expression in its body. You can use the return keyword for an early exit, but most Rust code just leaves the semicolon off the last line.

What happens if you add a semicolon by mistake? If you write x + 1; in a function that expects to return an i32, you'll get a "mismatched types" error. The semicolon turns it into a statement, returning a "unit type" () instead of the number the compiler was looking for.


Summary

  • Use fn and snake_case for functions.
  • Always specify types for parameters.
  • Statements perform actions; expressions produce values.
  • The last expression in a function (no semicolon) is the return value.