Beginner Level

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_case convention
  • Functions can be defined before or after they're called
  • The main function 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 happens

Practice 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

  1. Forgetting parameter types: fn my_func(x) āŒ → fn my_func(x: i32) āœ…
  2. Adding semicolon to return expression: return x + 1; in return position
  3. Not specifying return type: fn add(a: i32, b: i32) when returning a value
  4. 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_case for function names

Ready for Chapter 4? → Control Flow

šŸ¦€ Rust Programming Tutorial

Learn from Zero to Advanced

Built with Next.js and Tailwind CSS • Open Source