Back to Blog

[Rust] Understanding Functions: From Basics to Return Values

Functions are the building blocks of any Rust program. You’ve already seen the main function, but today we’re going to dive deeper into how you can create 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.");
}

Cool Fact: In Rust, it doesn't matter if you define another_function before or after the main function. As long as it's defined somewhere the caller can see, the compiler is happy! ✅

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 is a super important distinction 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 Rustaceans prefer just leaving 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 do work; Expressions result in values. 💎
  • The last expression in a function (no semicolon!) is the return value. 🎁

Comments