[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.
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."
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.
| Type | Description | Example |
|---|---|---|
| Statement | Instructions that perform an action but do not return a value. | let y = 6; |
| Expression | Code 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:
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 ->.
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
fnandsnake_casefor functions. - Always specify types for parameters.
- Statements perform actions; expressions produce values.
- The last expression in a function (no semicolon) is the return value.