1. rust
  2. /basics
  3. /functions

Functions

Functions are fundamental building blocks in Rust. They allow you to organize code into reusable pieces and create clear abstractions. Rust functions follow specific rules about parameters, return values, and the distinction between statements and expressions.

Function Basics

Functions in Rust are declared using the fn keyword:

fn main() {
    println!("Hello, world!");
    greet();
}

fn greet() {
    println!("Hello from the greet function!");
}

Function Naming Convention

Rust uses snake_case for function names:

fn calculate_area() { }
fn get_user_name() { }
fn is_valid_email() { }

// Not recommended:
// fn calculateArea() { }  // camelCase
// fn GetUserName() { }    // PascalCase

Function Parameters

Functions can take parameters of specified types:

fn main() {
    greet_person("Alice");
    let result = add(5, 3);
    println!("5 + 3 = {}", result);
}

fn greet_person(name: &str) {
    println!("Hello, {}!", name);
}

fn add(x: i32, y: i32) -> i32 {
    x + y
}

Multiple Parameters

fn describe_person(name: &str, age: u32, is_student: bool) {
    println!("{} is {} years old", name, age);
    if is_student {
        println!("{} is a student", name);
    } else {
        println!("{} is not a student", name);
    }
}

fn main() {
    describe_person("Bob", 25, false);
}

Parameter Patterns

You can destructure parameters:

fn print_coordinates((x, y): (i32, i32)) {
    println!("Point is at ({}, {})", x, y);
}

fn print_name_and_age(person: (&str, u32)) {
    let (name, age) = person;
    println!("{} is {} years old", name, age);
}

fn main() {
    print_coordinates((10, 20));
    print_name_and_age(("Charlie", 30));
}

Return Values

Functions can return values using the -> syntax:

fn square(x: i32) -> i32 {
    x * x  // Expression without semicolon
}

fn is_even(x: i32) -> bool {
    x % 2 == 0
}

fn get_greeting() -> String {
    String::from("Hello, Rust!")
}

fn main() {
    let num = 4;
    println!("{} squared is {}", num, square(num));
    println!("{} is even: {}", num, is_even(num));
    println!("{}", get_greeting());
}

Early Return

Use the return keyword for early returns:

fn divide(x: f64, y: f64) -> Option<f64> {
    if y == 0.0 {
        return None;  // Early return
    }
    Some(x / y)
}

fn check_password(password: &str) -> Result<(), &str> {
    if password.len() < 8 {
        return Err("Password too short");
    }
    if !password.chars().any(|c| c.is_numeric()) {
        return Err("Password must contain a number");
    }
    Ok(())
}

Statements vs Expressions

Understanding the difference between statements and expressions is crucial:

Statements

Statements perform actions but don't return values:

fn main() {
    let x = 5;        // Statement
    let y = 10;       // Statement
    let z = add(x, y); // Statement (assignment)
}

fn add(a: i32, b: i32) -> i32 {
    let sum = a + b;  // Statement
    sum               // Expression (returned)
}

Expressions

Expressions evaluate to values:

fn main() {
    let x = 5;
    
    // Block expression
    let y = {
        let inner = 3;
        inner + 1      // Expression: evaluates to 4
    };
    
    // if expression
    let max = if x > y { x } else { y };
    
    println!("y: {}, max: {}", y, max);
}

Common Expression Pitfall

Adding a semicolon turns an expression into a statement:

fn returns_value() -> i32 {
    5  // Expression: returns 5
}

fn returns_unit() -> () {
    5; // Statement: returns () (unit type)
}

// This would cause a compile error:
// fn wrong() -> i32 {
//     5; // Error: expected i32, found ()
// }

Function Ownership and Borrowing

Functions follow Rust's ownership rules:

Taking Ownership

fn take_ownership(s: String) {
    println!("{}", s);
} // s goes out of scope and is dropped

fn main() {
    let s = String::from("hello");
    take_ownership(s);
    // println!("{}", s); // Error: s has been moved
}

Borrowing with References

fn calculate_length(s: &String) -> usize {
    s.len()
} // s goes out of scope, but the data it refers to is not dropped

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);
    println!("Length of '{}' is {}", s, len); // s is still valid
}

Mutable References

fn append_world(s: &mut String) {
    s.push_str(", world!");
}

fn main() {
    let mut s = String::from("hello");
    append_world(&mut s);
    println!("{}", s); // "hello, world!"
}

Advanced Function Concepts

Function Pointers

Functions can be stored in variables and passed as arguments:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

fn apply_operation(x: i32, y: i32, op: fn(i32, i32) -> i32) -> i32 {
    op(x, y)
}

fn main() {
    let result1 = apply_operation(5, 3, add);
    let result2 = apply_operation(5, 3, multiply);
    
    println!("5 + 3 = {}", result1);
    println!("5 * 3 = {}", result2);
    
    // Store function in variable
    let operation: fn(i32, i32) -> i32 = add;
    println!("Using stored function: {}", operation(10, 20));
}

Higher-Order Functions

Functions that take other functions as parameters:

fn apply_to_each<F>(vec: &mut Vec<i32>, func: F)
where
    F: Fn(i32) -> i32,
{
    for item in vec.iter_mut() {
        *item = func(*item);
    }
}

fn double(x: i32) -> i32 {
    x * 2
}

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    apply_to_each(&mut numbers, double);
    println!("{:?}", numbers); // [2, 4, 6, 8, 10]
}

Closures

Closures are anonymous functions that can capture their environment:

fn main() {
    let x = 4;
    
    // Closure that captures x
    let equal_to_x = |z| z == x;
    
    let y = 4;
    assert!(equal_to_x(y));
    
    // Closure with explicit types
    let add_one = |x: i32| -> i32 { x + 1 };
    println!("5 + 1 = {}", add_one(5));
    
    // Multi-line closure
    let expensive_closure = |num| {
        println!("Calculating slowly...");
        std::thread::sleep(std::time::Duration::from_secs(1));
        num
    };
    
    println!("Result: {}", expensive_closure(42));
}

Closure Capture Modes

fn main() {
    let x = vec![1, 2, 3];
    
    // By reference (Fn)
    let print_x = || println!("x: {:?}", x);
    print_x();
    println!("x is still available: {:?}", x);
    
    // By mutable reference (FnMut)
    let mut y = vec![1, 2, 3];
    let mut modify_y = || y.push(4);
    modify_y();
    println!("y after modification: {:?}", y);
    
    // By value (FnOnce)
    let z = vec![1, 2, 3];
    let consume_z = move || {
        println!("z: {:?}", z);
        z // z is moved into the closure
    };
    let result = consume_z();
    // println!("{:?}", z); // Error: z has been moved
}

Generic Functions

Functions can be generic over types:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    
    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);
    
    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

Error Handling in Functions

Using Result Type

use std::num::ParseIntError;

fn parse_number(s: &str) -> Result<i32, ParseIntError> {
    s.parse()
}

fn add_numbers(a: &str, b: &str) -> Result<i32, ParseIntError> {
    let num_a = parse_number(a)?;
    let num_b = parse_number(b)?;
    Ok(num_a + num_b)
}

fn main() {
    match add_numbers("10", "20") {
        Ok(result) => println!("Sum: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

Using Option Type

fn find_first_word(s: &str) -> Option<&str> {
    s.split_whitespace().next()
}

fn get_file_extension(filename: &str) -> Option<&str> {
    filename.rfind('.').map(|i| &filename[i + 1..])
}

fn main() {
    if let Some(word) = find_first_word("hello world") {
        println!("First word: {}", word);
    }
    
    match get_file_extension("document.pdf") {
        Some(ext) => println!("Extension: {}", ext),
        None => println!("No extension found"),
    }
}

Function Documentation

Document your functions using doc comments:

/// Calculates the area of a rectangle.
/// 
/// # Arguments
/// 
/// * `width` - The width of the rectangle
/// * `height` - The height of the rectangle
/// 
/// # Examples
/// 
/// ```
/// let area = calculate_area(10.0, 5.0);
/// assert_eq!(area, 50.0);
/// ```
fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

/// Divides two numbers, returning None if the divisor is zero.
/// 
/// # Examples
/// 
/// ```
/// assert_eq!(safe_divide(10.0, 2.0), Some(5.0));
/// assert_eq!(safe_divide(10.0, 0.0), None);
/// ```
fn safe_divide(dividend: f64, divisor: f64) -> Option<f64> {
    if divisor == 0.0 {
        None
    } else {
        Some(dividend / divisor)
    }
}

Best Practices

1. Keep Functions Small and Focused

// Good: Single responsibility
fn validate_email(email: &str) -> bool {
    email.contains('@') && email.contains('.')
}

fn send_welcome_email(email: &str) -> Result<(), String> {
    if !validate_email(email) {
        return Err("Invalid email".to_string());
    }
    // Send email logic
    Ok(())
}

2. Use Descriptive Names

// Good
fn calculate_monthly_payment(principal: f64, rate: f64, months: u32) -> f64 {
    // Implementation
    0.0
}

// Avoid
fn calc(p: f64, r: f64, m: u32) -> f64 {
    0.0
}

3. Prefer Returning Values over Mutation

// Good: Functional style
fn add_prefix(s: String, prefix: &str) -> String {
    format!("{}{}", prefix, s)
}

// Less ideal: Mutation
fn add_prefix_mut(s: &mut String, prefix: &str) {
    s.insert_str(0, prefix);
}

4. Use Type Annotations When Helpful

// Clear parameter types
fn process_data(data: &[u8], format: &str) -> Result<String, Box<dyn std::error::Error>> {
    // Implementation
    Ok(String::new())
}

Functions in Rust are powerful tools for creating clean, reusable, and safe code. Understanding ownership, borrowing, expressions vs statements, and advanced concepts like closures and generics will help you write effective Rust programs.