Rust Functions and Closures


Example:

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

fn main() {
    let sum = add(5, 3);
    println!("Sum: {}", sum);
}
                    
                  
  • Parameters: A function can be defined either without parameters or with zero or more parameters, each parameter specifying the name and type.
  • Return Type: The return type is specified after an arrow (->).If there is no return type, the default is an empty tuple ().

Closures:

Closures are anonymous functions which you can assign in special variable or pass as arguments to other functions.

Just as functions do, closures keep track of variables from the outside environment.

Example:

                    
    fn main() {
    let add = |a: i32, b: i32| -> i32 { a + b };

    let sum = add(5, 3);
    println!("Sum: {}", sum);
}
                    
                  

Syntax: Closures are defined using vertical bars | to delimit the parameter list, followed by the function body.

Capturing Variables with Closures

Closures can capture variables from their surrounding environment in three ways: the way they borrow immutably, mutably, or take ownership dictates the existing structure.

Example:

                    
fn main() {
    let x = 10;
    let add_to_x = |a: i32| -> i32 { a + x }; // Borrow x immutably

    let result = add_to_x(5);
    println!("Result: {}", result); // Output: Result: 15

    let mut y = 10;
    {
        let mut add_to_y = |a: i32| {
            y += a; // Borrow y mutably
        };
        add_to_y(5);
    }
    println!("y: {}", y); // Output: y: 15

    let z = vec![1, 2, 3];
    let consume_z = move |a: i32| -> i32 { // Take ownership of z
        a + z.len() as i32
    };

    println!("Result with z: {}", consume_z(5)); // Output: Result with z: 8
}
                    
                  
  • Immutably: The closure captures the variable by borrowing it immutably.
  • Mutably: The closure captures the variable by borrowing it mutably.
  • By Value: The closure captures the variable by taking ownership, using the move keyword.

Higher-Order Functions

Higher order functions are the functions that accept one or more functions as their arguments or return a function as their output.

Rust has function higher-order features that make programming with functional style possible.

Example:

                    
fn apply(f: F, a: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(a)
}

fn main() {
    let add_one = |x: i32| x + 1;
    let result = apply(add_one, 5);

    println!("Result: {}", result); // Output: Result: 6
}
                    
                  

Generics and Traits: The apply function uses generics and the Fn trait to accept any closure that matches the signature Fn(i32) -> i32.

Another example using 'map':

                    
fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let doubled: Vec = numbers.iter().map(|x| x * 2).collect();

    println!("Doubled: {:?}", doubled); // Output: Doubled: [2, 4, 6, 8, 10]
}
                    
                  

Iterators and Closures: The map method is a higher-function that works with closures and any iterator's element.