🚀
14. Closures

Closures

  • Clousures are like functions, expect that are anonymous that can be stored at variables
  • They can capture values from the scope in which they're defined
  • They are a part of functions which are explicity exposed to users
  • Short and relevant in a small context
use std::thread;
use std::time::Duration;
 
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
 
    intensity
}
 
fn main() {
    let simulated_insensity = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_insensity, simulated_random_number);
}
 
 
fn generate_workout(intensity: u32, random_number:u32) {
 
    let expensive_result = simulated_expensive_calculation(intensity);
 
    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
           expensive_result
        );
 
        println!(
            "Next, do {} situps",
           expensive_result
        )
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run from {} minutes!",
               expensive_result
            )
        }
    }
}
use std::thread;
use std::time::Duration;
 
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
 
    intensity
}
 
fn main() {
    let simulated_insensity = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_insensity, simulated_random_number);
}
 
 
fn generate_workout(intensity: u32, random_number:u32) {
    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            simulated_expensive_calculation(intensity)
        );
 
        println!(
            "Next, do {} situps",
            simulated_expensive_calculation(intensity)
        )
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run from {} minutes!",
                simulated_expensive_calculation(intensity)
            )
        }
    }
}
let example_closure = |x| x;
 
let s = example_closure(String::from("hello"));
let n = example_closure(5);
$ cargo run
 
error[E0308]: mismatched types
  --> src/main.rs:15:29
   |
 5 |     let n = example_closure(5);
   |             --------------- ^- help: try using a conversion method: `.to_string()`
   |             |               |
   |             |               expected `String`, found integer
   |             arguments to this function are incorrect
   |
🚫

Error: mismatched types

  • Closures definations can have only one concrete type defination for each input parameter
  • Initially, compiler will infer the type of the closure is String
fn generate_workout(intensity: u32, random_number: u32) {
    let expensive_closure = |num| {
        println!("calculating slowly ... ");
        thread::sleep(Duration::from_secs(2));
        num
    };
 
    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps", expensive_closure(intensity))
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!("Today, run from {} minutes!", expensive_closure(intensity))
        }
    }
}
  • expensive_closure is an expensive operation, that is being called multiple times (not optimal)

  • to avoid this we are going to create a memoization pattern using struct, which will hold the closure and the results of the closure

struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}
  • Cacher is a generic struct that holds a closure and an optional value
  • calculation is a closure that takes a u32 and returns a u32
  • value is used to store the result of the closure
impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }
 
    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}
  • Implementing Cacher with a method new that takes a closure and returns a Cacher instance
fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_closure = Cacher::new(|num| {
        println!("calculating slowly ... ");
        thread::sleep(Duration::from_secs(2));
        num
    });
 
    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure.value(intensity));
        println!("Next, do {} situps", expensive_closure.value(intensity))
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!("Today, run from {} minutes!", expensive_closure.value(intensity))
        }
    }
}

Originally

$ cargo run
 
calculating slowly ...
Today, do 10 pushups!
calculating slowly ...
Next, do 10 situps

After Memoization

$ cargo run
 
calculating slowly ...
Today, do 10 pushups!
Next, do 10 situps
  • Adding Generics to the Cacher struct, we can now use any closure that takes a u32 and returns a u32 with the Cacher struct
struct Cacher<T, U>
where
    T: Fn(U) -> U,
{
    calculation: T,
    value: Option<U>,
}
 
impl<T, U> Cacher<T, U>
where
    T: Fn(U) -> U,
    U: Clone,
{
    fn new(calculation: T) -> Cacher<T, U> {
        Cacher {
            calculation,
            value: None,
        }
    }
 
    fn value(&mut self, arg: U) -> U {
        match &self.value {
            Some(v) => v.clone(),  // Return the cached value
            None => {
                let v = (self.calculation)(arg.clone());  // Use and store the cloned argument
                self.value = Some(v.clone());  // Store the cloned value
                v  // Return the calculated value
            }
        }
    }
}
  • Closures take ownership of the values they use in their environment
fn main() {
    let x = vec![1, 2, 3];
 
    let equal_to_x = move |z| z == x;
 
    println!("can't use x here: {:?}", x);
 
    let y = vec![1, 2, 3];
 
    assert!(equal_to_x(y));
}
🚫

Error: borrow of moved value: x

Clousure Types:

  • FnOnce consumes the variables it captures from its enclosing scope, known as the closure's environment
  • FnMut can change the environment because it mutably borrows values
  • Fn borrows values from the environment immutably

© 2024 Driptanil Datta.All rights reserved

Made with Love ❤️

Last updated on Mon Oct 20 2025