Welcome to our eighth lesson. In our previous session, we explored Rust's control flow constructs and saw how if and loop can act as expressions that evaluate to a value. This expression-oriented philosophy is a cornerstone of Rust's design, and today, we'll see how it profoundly influences the way we write and use functions.
This lesson focuses on defining functions with typed parameters and return values. While the concept of functions will be familiar from your experience with Python and Swift, we'll pay close attention to Rust's specific syntax and its elegant, expression-based approach to returning values, which builds directly on what we've just learned.
1. Function Syntax and Parameters
In Rust, you declare a function using the fn keyword, which is a bit more concise than Swift's func. The conventional style for function names is snake_case.
Let's begin by reviewing the fundamental syntax for defining, calling, and passing parameters to functions in the official Rust documentation.
Functions - The Rust Programming Language
This chapter from "The Rust Programming Language" book covers the essential syntax for functions and parameters.
Please read the following sections: Start from the beginning, Functions, to understand the basic fn syntax and naming conventions. Continue with the section on Parameters. Pay close attention to two key points: the distinction between parameters (in the definition) and arguments (in the call), and the fact that type annotations for parameters are mandatory in Rust.
As you read, you'll notice a key similarity to Swift: Rust requires you to explicitly declare the type of each function parameter. This design choice enables the compiler to verify type correctness without needing to infer types across function boundaries, leading to clearer function signatures and more helpful error messages.
The following diagram clarifies the terminology used in the book.

2. Statements, Expressions, and Return Values
Now we arrive at the most distinct aspect of Rust functions. As we touched on in the last lesson, Rust is an expression-based language. This has a direct impact on how functions return values.
A function's body is made up of a series of statements that perform actions, optionally ending in an expression that evaluates to a value.
- Statements are instructions that do not return a value. For example,
let x = 5;is a statement. Notice the semicolon at the end. - Expressions evaluate to a resulting value. For example,
5 + 6is an expression that evaluates to11. A code block{}can also be an expression.
This distinction is the key to understanding Rust's return mechanism. To see it in action, let's watch a detailed walkthrough.
Rust Tutorial #8 - Functions, Expressions & Statements
The video "Rust Tutorial #8 - Functions, Expressions & Statements" by Tech With Tim provides an excellent step-by-step explanation of how statements and expressions relate to function returns.
Please watch the following segments: Statements vs. Expressions: Begin with the detailed explanation from this section. The presenter demonstrates a key concept: a block of code can be an expression that evaluates to a value, and omitting the final semicolon is what makes it an expression rather than a statement. Implicit Returns: Continue from here to see how this concept is applied to return a value from a function. You must declare the return type using an arrow ->, and the function implicitly returns the value of the final expression. Explicit return: Finally, watch the segment from here, which introduces the return keyword. Note that this is typically used for "early returns," while the idiomatic way to return from the end of a function is to use the final expression.
As the video demonstrated, there are two primary ways to return a value from a function in Rust:
- Implicitly: Make the value you want to return the very last expression in the function's body. Do not add a semicolon. This is the most common and idiomatic approach in Rust.
- Explicitly: Use the
returnkeyword. This is necessary when you need to return from the middle of a function, such as inside anifblock, before the final expression is reached.
Let's solidify this by reading the official documentation on the topic. It includes an example of the exact compiler error you get when you mistakenly add a semicolon to a final expression.
Functions - The Rust Programming Language
This section of the Rust book provides a concise, text-based summary of everything covered in the video.
Read the entire section Functions with Return Values. Pay close attention to the error message mismatched types: expected i32, found ().
The error message expected i32, found () is fundamental. The empty tuple, (), is called the "unit type." It's Rust's way of representing "no return value." When you add a semicolon to an expression like x + 1;, you turn it into a statement. Statements don't evaluate to a value, so they are considered to have the type (). The compiler error is telling you that the function signature promised an i32, but the body ended with a statement that returned ().

Here is a complete example putting these concepts together:
fn main() {
let x = 5;
let y = 3;
let sum = add_and_double(x, y);
println!("The result of adding {x} and {y}, then doubling, is {sum}");
let another_sum = add_and_double_early_exit(10, 2);
println!("The second result is {another_sum}");
}
// This function uses an implicit return.
// The final expression `(x + y) * 2` is returned.
fn add_and_double(x: i32, y: i32) -> i32 {
(x + y) * 2
}
// This function uses an explicit return for an early exit.
fn add_and_double_early_exit(x: i32, y: i32) -> i32 {
let sum = x + y;
if sum > 10 {
return sum; // Early exit using the 'return' keyword
}
// Implicit return for the normal case
sum * 2
}
Conclusion
In this lesson, we've covered the mechanics of creating functions in Rust. While the core ideas are familiar, Rust's expression-based nature provides a distinct and elegant way of handling return values that encourages a concise and functional style.
Here are the main takeaways:
- Functions are declared with the
fnkeyword and named usingsnake_case. - You must annotate the type of every function parameter.
- The return type is specified after an arrow (
-> Type). If no return type is specified, the function is assumed to return the unit type(). - The value of the final expression in a function's body is implicitly returned. This expression must not have a trailing semicolon.
- The
returnkeyword is used for returning a value before the end of the function (an "early return").
This lesson concludes our first module on Rust's fundamentals. You now have a solid grasp of the basic syntax for variables, control flow, and functions. In our next module, we will dive into what truly sets Rust apart: its ownership and memory management model. This is where we will begin to understand how Rust achieves memory safety without a garbage collector.