Hello everyone, new episode of rust series is here, today we will discuss closures in Rust. Closures, also called lambdas, are functions that can capture the enclosing environment.
Closures vs fn functions
- you don't have to to annotate types (it's optional)
- you don't have to annotate return type (it's optional)
- use || instead of ()
- body {} is optional
- captures outer environment
Basic closure definition
fn main() {
let power = | x | x * x;
let res = power(2);
println!("Result: {}", res);
}
# output
Result: 4
This example above is pretty straight and without any issues.
Borrowing and ownership around closures
let mut addition = 2;
let plus_two = | x | x + addition;
let res2 = plus_two(10);
addition = 3;
# output
error: cannot assign to `addition` because it is borrowed
Changing value of addition cannot be done as addition is already borrowed by closure
Even more aggressive ownership
let nums = vec![1, 2, 3];
let takes_nums = || nums;
println!("{:?}", nums);
# output
error: use of moved value: `nums`
This cannot be done as closure must take ownership of vector content here. Note that this is not an issue with array or other read-only structures or those that can by simply copied.
Ownership of the copy
Rust provides keyword move so closure can take ownership of its environment. Sound strange but basically it creates copy of a value used inside closure.
let mut num = 10;
let mut add_num = move |x: i32| {
num += x;
println!("num value in closure: {}", num);
};
println!("num value before calling closure: {}", num);
add_num(10);
println!("num value after calling closure: {}", num);
# output
num value before calling closure: 10
num value in closure: 20
num value after calling closure: 10
Version using the original
Now check this code below that is using the original variable. Note that variable scope is slightly updated not to interfere with ownership protection.
let mut num = 10;
println!("num value before calling closure: {}", num);
{
let mut add_num = |x: i32| {
num += x;
println!("num value in closure: {}", num);
};
add_num(10);
}
println!("num value after calling closure: {}", num);
# output
num value before calling closure: 10
num value in closure: 20
num value after calling closure: 20
Now we truly work with original num variable, not a copy.
Closure as function argument
Closure can be used as an regular function argument.
fn main() {
fn call_with_ten<F>(some_closure: F) -> i32
where F: Fn(i32) -> i32 {
some_closure(10)
}
let powered_result = call_with_ten(|x| x * x);
let half_result = call_with_ten(|x| x / 2);
println!("pow result: {}", powered_result);
println!("half result: {}", half_result);
}
# output
pow result: 100
half result: 5
We've defined function that will call various closures with argument 10.
Closure as function pointer
We can also use functions as closure input for other function, check code below providing the same functionality as last example.
fn main() {
fn call_with_ten(some_closure: &Fn(i32) -> i32) -> i32 {
some_closure(10)
}
fn powered_function(i: i32) -> i32{
i * i
}
fn half_function(i: i32) -> i32 {
i / 2
}
let powered_result = call_with_ten(&powered_function);
let half_result = call_with_ten(&half_function);
println!("pow result: {}", powered_result);
println!("half result: {}", half_result);
}
# output
pow result: 100
half result: 5
Closure factory
Closure factory is some function that returns closure. It's quite common pattern. There are some principle that must be used to cross some limitations
- we need to Box Fn to cross limited closure environment
- we need to copy factory scoped variables (if any)
After applying these principles we can create factory like this
fn main() {
fn factory_function() -> Box<Fn(i32) -> i32> {
let num = 10;
Box::new(move |x| x + num)
}
let produced_function = factory_function();
let result = produced_function(10);
println!("result: {}", result );
}
# output
result: 20
Postfix
That's all for today, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.
Meanwhile you can also check the official documentation for closures and if you wish.