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_closureis 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>,
}Cacheris a generic struct that holds a closure and an optional valuecalculationis a closure that takes au32and returns au32valueis 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
Cacherwith a methodnewthat takes a closure and returns aCacherinstance
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 situpsAfter Memoization
$ cargo run
calculating slowly ...
Today, do 10 pushups!
Next, do 10 situps- Adding Generics to the
Cacherstruct, we can now use any closure that takes au32and returns au32with theCacherstruct
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:
FnOnceconsumes the variables it captures from its enclosing scope, known as the closure's environmentFnMutcan change the environment because it mutably borrows valuesFnborrows values from the environment immutably