Functions in Rust
Introduction
Functions are fundamental building blocks in Rust. This chapter will cover everything you need to know about defining and using functions effectively.
Basic Function Syntax
The basic syntax of a Rust function is:
fn function_name(parameters) -> return_type {
// function body
}
Rust uses snake_case as the conventional style for function names:
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Hello from another function!");
}
Output:
Hello, world!
Hello from another function!
> Note: Rust doesn't care where you define your functions in the source file. As long as they're defined somewhere in the scope, they can be called.
Function Parameters
Parameter Declaration
Functions can take parameters, which must have their types declared:
fn main() {
print_numbers(5, 6);
}
fn print_numbers(x: i32, y: i32) {
println!("x is: {}", x);
println!("y is: {}", y);
}
Output:
x is: 5
y is: 6
Parameter Best Practices
- Use descriptive parameter names
- Keep the number of parameters minimal
- Consider using structs for multiple related parameters
- Document parameter requirements
Statements and Expressions
Rust makes an important distinction between statements and expressions:
Statements
- Perform actions but don't return values
- End with a semicolon
- Examples:
let x = 6; // Statement
let y = 7; // Statement
> Important: You cannot assign a statement to a variable:
let x = (let y = 6); // This will NOT work!
Expressions
- Evaluate to a value
- Don't end with a semicolon
- Examples:
5 + 6 // Expression
x * y // Expression
some_func() // Expression
Expression Blocks
Rust allows complex expressions using blocks:
fn main() {
let x = 5;
let y = {
let temp = 3;
temp + 1 // Note: no semicolon here!
};
println!("x is: {}", x);
println!("y is: {}", y);
}
Output:
x is: 5
y is: 4
Return Values
Explicit Returns
Functions can return values using the ->
syntax:
fn add(a: i32, b: i32) -> i32 {
return a + b; // Explicit return
}
Implicit Returns
The last expression in a function will be returned implicitly:
fn subtract(a: i32, b: i32) -> i32 {
a - b // Implicit return (no semicolon!)
}
Important Rules
- Return type must be declared explicitly
- All return paths must return the same type
- Early returns must use the
return
keyword - The last expression can be an implicit return
Nested Functions
Rust allows function definitions within other functions:
fn main() {
fn five() -> i32 {
5
}
println!("The value of five() is: {}", five());
}
Advanced Function Patterns
Multiple Return Values Using Tuples
fn calculate_stats(numbers: &[i32]) -> (i32, i32, i32) {
let sum = numbers.iter().sum();
let min = numbers.iter().min().unwrap_or(&0);
let max = numbers.iter().max().unwrap_or(&0);
(*min, *max, sum)
}
Functions with Generic Types
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
Best Practices
-
Function Naming
- Use descriptive names
- Follow snake_case convention
- Verb-noun combinations for actions
-
Function Size
- Keep functions small and focused
- Single responsibility principle
- Extract complex logic into separate functions
-
Return Values
- Be consistent with return types
- Use Result for error handling
- Consider using Option for nullable returns
-
Documentation
- Document complex functions
- Include examples in documentation
- Explain parameter requirements
Common Pitfalls
- Forgetting Return Type
// Wrong
fn add(a: i32, b: i32) {
a + b // This won't work!
}
// Correct
fn add(a: i32, b: i32) -> i32 {
a + b
}
- Semicolon in Expression Returns
// Wrong - returns ()
fn get_five() -> i32 {
5; // Semicolon makes this a statement!
}
// Correct - returns 5
fn get_five() -> i32 {
5 // No semicolon
}
- Type Mismatch in Returns
// Wrong
fn get_number(flag: bool) -> i32 {
if flag {
42
} else {
"not a number" // Type mismatch!
}
}
// Correct
fn get_number(flag: bool) -> i32 {
if flag {
42
} else {
0
}
}
Next Steps
After mastering Rust functions:
- Learn about closures and higher-order functions
- Explore function traits and trait bounds
- Study error handling with Result and Option
- Practice writing generic functions
- Understand async functions and futures
Remember: Functions are the building blocks of your Rust programs. Taking time to understand and properly implement them will lead to more maintainable and efficient code.