🚀
14. Closures
Languages

Closures

Closures are like functions, except that they are anonymous and can be stored in variables

Mar 202510 min read

Closures

  • Closures are like functions, except that they are anonymous and can be stored in variables
  • They can capture values from the scope in which they're defined
  • They are a part of functions which are explicitly 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_intensity = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_intensity, 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_intensity = 10;
    let simulated_random_number = 7;
 
    generate_workout(simulated_intensity, 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);
Execution Output
$ cargo run

error[E0308]: mismatched types
--> src/main.rs:15:29
|
🚫

Error: mismatched types

  • Closure definitions can have only one concrete type definition 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

Execution Output
$ cargo run

calculating slowly ...
Today, do 10 pushups!
calculating slowly ...

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

Closure 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

© 2026 Driptanil Datta. All rights reserved.

Software Developer & Engineer

Disclaimer:The content provided on this blog is for educational and informational purposes only. While I strive for accuracy, all information is provided "as is" without any warranties of completeness, reliability, or accuracy. Any action you take upon the information found on this website is strictly at your own risk.

Copyright & IP:Certain technical content, interview questions, and datasets are curated from external educational sources to provide a centralized learning resource. Respect for original authorship is maintained; no copyright infringement is intended. All trademarks, logos, and brand names are the property of their respective owners.

System Operational

Built with Love ❤️ | Last updated: Mar 16 2026