Functions
Chapter 3: Functions š§
Functions are the building blocks of Rust programs. They help you organize code, avoid repetition, and make your programs more readable. Let's dive into how functions work in Rust!
Function Basics
Declaring Functions
Functions are declared using the fn keyword:
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}Key Points:
- Function names use
snake_caseconvention - Functions can be defined before or after they're called
- The
mainfunction is the entry point of your program
Functions with Parameters
fn main() {
another_function(5);
print_labeled_measurement(5, 'h');
}
fn another_function(x: i32) {
println!("The value of x is: {}", x);
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {}{}", value, unit_label);
}Important: You must declare the type of each parameter!
Statements vs Expressions
Understanding the difference is crucial in Rust:
Statements
Statements perform actions but don't return values:
fn main() {
let y = 6; // This is a statement
}Expressions
Expressions evaluate to a value:
fn main() {
let y = {
let x = 3;
x + 1 // No semicolon! This is an expression
};
println!("The value of y is: {}", y); // Prints: 4
}Key Rule: Expressions don't end with semicolons. Adding a semicolon turns an expression into a statement.
Functions with Return Values
Functions can return values using the -> syntax:
fn five() -> i32 {
5 // No semicolon - this is an expression
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}Using return Keyword
fn plus_one(x: i32) -> i32 {
return x + 1; // Explicit return
}
// More idiomatic Rust:
fn plus_one_idiomatic(x: i32) -> i32 {
x + 1 // Implicit return (no semicolon)
}
fn main() {
let result1 = plus_one(5);
let result2 = plus_one_idiomatic(5);
println!("Results: {}, {}", result1, result2);
}Multiple Return Values
Use tuples to return multiple values:
fn calculate_length_and_area(width: u32, height: u32) -> (u32, u32) {
let area = width * height;
let perimeter = 2 * (width + height);
(perimeter, area) // Return tuple
}
fn main() {
let (perimeter, area) = calculate_length_and_area(10, 20);
println!("Perimeter: {}, Area: {}", perimeter, area);
}Early Returns
fn divide(dividend: f64, divisor: f64) -> f64 {
if divisor == 0.0 {
println!("Error: Division by zero!");
return 0.0; // Early return
}
dividend / divisor
}
fn main() {
println!("10 / 2 = {}", divide(10.0, 2.0));
println!("10 / 0 = {}", divide(10.0, 0.0));
}Function Examples
Example 1: Temperature Converter
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
celsius * 9.0 / 5.0 + 32.0
}
fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
(fahrenheit - 32.0) * 5.0 / 9.0
}
fn main() {
let temp_c = 25.0;
let temp_f = celsius_to_fahrenheit(temp_c);
println!("{}°C is {}°F", temp_c, temp_f);
println!("{}°F is {}°C", temp_f, fahrenheit_to_celsius(temp_f));
}Example 2: Mathematical Operations
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
fn divide(a: f64, b: f64) -> f64 {
if b != 0.0 {
a / b
} else {
0.0
}
}
fn main() {
let x = 10;
let y = 5;
println!("{} + {} = {}", x, y, add(x, y));
println!("{} - {} = {}", x, y, subtract(x, y));
println!("{} * {} = {}", x, y, multiply(x, y));
println!("{} / {} = {}", x, y, divide(x as f64, y as f64));
}Function Scope and Ownership
Functions have their own scope:
fn main() {
let s = String::from("hello");
takes_ownership(s); // s is moved into the function
// println!("{}", s); // This would cause an error!
let x = 5;
makes_copy(x); // x is copied (integers implement Copy)
println!("x is still valid: {}", x);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string goes out of scope and is dropped
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer goes out of scope, but nothing special happensPractice Exercises
Exercise 1: Fibonacci Function
Write a function that calculates the nth Fibonacci number:
fn fibonacci(n: u32) -> u32 {
// Your implementation here
}
fn main() {
for i in 0..10 {
println!("fibonacci({}) = {}", i, fibonacci(i));
}
}Exercise 2: Is Even/Odd
Write functions to check if a number is even or odd:
fn is_even(n: i32) -> bool {
// Your implementation here
}
fn is_odd(n: i32) -> bool {
// Your implementation here
}
fn main() {
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for num in numbers {
println!("{} is even: {}, odd: {}", num, is_even(num), is_odd(num));
}
}Exercise 3: Calculator Function
Create a calculator function that takes two numbers and an operation:
fn calculate(a: f64, b: f64, operation: char) -> f64 {
// Your implementation here
// Handle +, -, *, /
}
fn main() {
println!("10 + 5 = {}", calculate(10.0, 5.0, '+'));
println!("10 - 5 = {}", calculate(10.0, 5.0, '-'));
println!("10 * 5 = {}", calculate(10.0, 5.0, '*'));
println!("10 / 5 = {}", calculate(10.0, 5.0, '/'));
}Common Mistakes
- Forgetting parameter types:
fn my_func(x)ā āfn my_func(x: i32)ā - Adding semicolon to return expression:
return x + 1;in return position - Not specifying return type:
fn add(a: i32, b: i32)when returning a value - Mixing statements and expressions: Understanding when to use semicolons
Advanced Function Concepts (Preview)
Coming in later chapters:
- Closures: Anonymous functions that can capture their environment
- Function pointers: Passing functions as arguments
- Higher-order functions: Functions that take other functions as parameters
What's Next?
In Chapter 4, we'll explore control flow - how to make decisions and repeat actions in your programs!
Key Takeaways
- ā Functions organize code and promote reusability
- ā Parameter types must be explicitly declared
- ā
Functions can return values using
->syntax - ā Expressions (no semicolon) vs statements (with semicolon)
- ā Functions have their own scope and follow ownership rules
- ā
Use
snake_casefor function names
Ready for Chapter 4? ā Control Flow