OneCompiler

Functions

Functions in Rust

Functions are fundamental building blocks in Rust. They allow you to organize code into reusable units.

Function Basics

Define a function using the fn keyword:

fn main() {
    greet();
}

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

Function Parameters

Functions can accept parameters:

fn main() {
    greet_user("Alice");
    greet_user("Bob");
}

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

Multiple Parameters

fn main() {
    print_sum(5, 10);
}

fn print_sum(x: i32, y: i32) {
    println!("{} + {} = {}", x, y, x + y);
}

Return Values

Use -> to specify return type:

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

fn add(x: i32, y: i32) -> i32 {
    x + y  // No semicolon - this is the return value
}

Explicit Return

fn main() {
    let result = calculate(10, 5, '+');
    println!("Result: {}", result);
}

fn calculate(a: i32, b: i32, op: char) -> i32 {
    if op == '+' {
        return a + b;
    } else if op == '-' {
        return a - b;
    }
    
    0  // Default return
}

Statements vs Expressions

Understanding the difference is crucial in Rust:

fn main() {
    let y = {
        let x = 3;
        x + 1  // Expression (no semicolon)
    };
    
    println!("y = {}", y);  // Prints: y = 4
}

Functions are Expressions

fn main() {
    let result = add_one(5);
    println!("Result: {}", result);
}

fn add_one(x: i32) -> i32 {
    x + 1  // The entire function body is an expression
}

Function Pointers

Functions can be stored in variables:

fn main() {
    let my_func: fn(i32, i32) -> i32 = add;
    let result = my_func(5, 3);
    println!("Result: {}", result);
}

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

Generic Functions

Functions can work with multiple types:

fn main() {
    let int_result = largest(5, 10);
    let char_result = largest('a', 'z');
    
    println!("Larger int: {}", int_result);
    println!("Larger char: {}", char_result);
}

fn largest<T: PartialOrd>(a: T, b: T) -> T {
    if a > b {
        a
    } else {
        b
    }
}

Methods

Methods are functions defined within the context of a struct:

struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // Method
    fn area(&self) -> f64 {
        self.width * self.height
    }
    
    // Associated function (like static method)
    fn new(width: f64, height: f64) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::new(10.0, 20.0);
    println!("Area: {}", rect.area());
}

Higher-Order Functions

Functions that take or return other functions:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let result = apply_to_vec(&numbers, double);
    println!("Doubled: {:?}", result);
}

fn apply_to_vec(vec: &Vec<i32>, f: fn(i32) -> i32) -> Vec<i32> {
    vec.iter().map(|&x| f(x)).collect()
}

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

Recursion

Functions can call themselves:

fn main() {
    println!("Factorial of 5: {}", factorial(5));
    println!("Fibonacci 10th: {}", fibonacci(10));
}

fn factorial(n: u32) -> u32 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

Function Overloading

Rust doesn't support traditional overloading, but you can use traits:

trait Display {
    fn display(&self);
}

impl Display for i32 {
    fn display(&self) {
        println!("Integer: {}", self);
    }
}

impl Display for String {
    fn display(&self) {
        println!("String: {}", self);
    }
}

fn main() {
    42.display();
    String::from("Hello").display();
}

Diverging Functions

Functions that never return use ! type:

fn main() {
    let x = 5;
    if x < 0 {
        crash("x cannot be negative");
    }
    println!("x is positive");
}

fn crash(msg: &str) -> ! {
    panic!("{}", msg);
}

Best Practices

1. Keep Functions Small

// Good
fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

fn calculate_perimeter(width: f64, height: f64) -> f64 {
    2.0 * (width + height)
}

// Less good
fn calculate_rectangle_properties(width: f64, height: f64) -> (f64, f64) {
    let area = width * height;
    let perimeter = 2.0 * (width + height);
    (area, perimeter)
}

2. Use Descriptive Names

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

// Less good
fn check(s: &str) -> bool {
    s.contains('@') && s.contains('.')
}

3. Document Your Functions

/// Calculates the nth Fibonacci number
/// 
/// # Arguments
/// * `n` - The position in the Fibonacci sequence
/// 
/// # Returns
/// The nth Fibonacci number
fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

Practical Examples

Password Validator

fn main() {
    let password = "MyP@ssw0rd";
    if is_strong_password(password) {
        println!("Password is strong");
    } else {
        println!("Password is weak");
    }
}

fn is_strong_password(password: &str) -> bool {
    password.len() >= 8 &&
    password.chars().any(|c| c.is_uppercase()) &&
    password.chars().any(|c| c.is_lowercase()) &&
    password.chars().any(|c| c.is_numeric()) &&
    password.chars().any(|c| !c.is_alphanumeric())
}

Temperature Converter

fn main() {
    let celsius = 25.0;
    let fahrenheit = celsius_to_fahrenheit(celsius);
    println!("{}°C = {}°F", celsius, fahrenheit);
    
    let fahrenheit = 77.0;
    let celsius = fahrenheit_to_celsius(fahrenheit);
    println!("{}°F = {}°C", fahrenheit, celsius);
}

fn celsius_to_fahrenheit(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

fn fahrenheit_to_celsius(f: f64) -> f64 {
    (f - 32.0) * 5.0 / 9.0
}