Exploring Closures in Rust – Part 9

Introduction: This article is the 9th in the "Rust Programming" series, totaling 27 articles. Click on the "Rust Language" tag to view other chapters!
The concept of “closures” is likely familiar to JavaScript users.
In simple terms, a closure is a function without a name, but it has many differences from a regular function.
Let’s get started.
1. Definition
|parameters| { statements } 
For example, in the following case;
fn main(){  let printvar=|i| {println!("{}",i)}    printvar("this is str");  //printvar(8)}
Above:
|i| {println!("{}",i)}
This is a closure definition. After defining it, we assign it to a variable printvar for easier use later.
So what is the type of “printvar”?
There are three types:
Fn: represents a closure that takes &T as a parameter FnMut: takes mut &T as a parameter FnOnce: takes a value by transfer
What type is printvar above?
Rust will determine the parameter type based on the first usage:
printvar("this is str")
Its parameter is a str, so we can consider the type of “printvar” as Fn because it takes &str as a parameter.
If we use it like this for the first time:
printvar(9)
Then Rust will determine the type of “printvar” as “Fn”.
2. Capturing Variables
Another feature of closures is that they can capture surrounding variables.
let j=100let add_j = |i| {i+j}; // Call let k = add_j(9);println!("k={}",k);
Here, it will display:
k=109
3. Using as Input Parameters
Note that closures can be used as function parameters, but there is one point:
You must specify the specific type of the closure
The types we mentioned earlier are the three types.
Let’s look at an example:
// Input closure, returns a function of type `i32`.fn apply_to_3<F>(f: F) -> i32 where// Declare closure type      F: Fn(i32) -> i32 {      f(3)}fn main() {    let double_int = |i| { i*2 };     println!("double three is {}",        apply_3(double_int));}
4. Using as Input Functions
Closures can also be passed as functions
fn call_me<F: Fn()>(f: F) {    f()}fn main(){  let closure = || println!("I'm a closure!");  call_me(closure)}
5. As Returns
Closures can be returned from functions, but there are several restrictions:
1. Must be one of the three types of impl
2. Must use move to transfer ownership of the object
3. Cannot use generics
Let’s look at an example:
fn create_fn() -> impl Fn() {let text = "Fn".to_owned();       move || println!("This is a: {}", text)}fn create_fnmut() -> impl FnMut() {let text = "FnMut".to_owned();      move || println!("This is a: {}", text)}fn create_fnonce() -> impl FnOnce() { let text = "FnOnce".to_owned();     move || println!("This is a: {}", text)}fn main() {  let fn_plain = create_fn();  let mut fn_mut = create_fnmut();  let fn_once = create_fnonce();      fn_plain();     fn_mut();      fn_once();}
6. Higher-Order Functions
Rust provides support for higher-order functions.
A higher-order function is a computation method similar to a stream, allowing for batch processing of data, with filtering, truncation, output, and other functions in between.
It’s a bit complex, let’s look at an example:
let upper=100;let sum_of_squared_odd_numbers: u32 =        (0..).map(|n| n * n)             // Square all natural numbers             .take_while(|&n| n < upper) // Take those less than the upper limit             .filter(|&n| is_odd(n))     // Take odd numbers             .fold(0, |sum, i| sum + i); // Finally sum them upprintln!("cal result is : {}", sum_of_squared_odd_numbers);
The above process is
1. Calculate the square sum
2. Truncate to 100
3. Check if this square sum is odd
4. Add these square sums together
You can see that a special point here is the truncation operation, meaning the entire process is lazily computed, not calculating all square sums at once, but based on a pipeline operation with upstream and downstream constraints!
Finally
That’s all for closures. If I gain further insights, I will continue to share with everyone.
I am Mingyue,
A Ruster!

Leave a Comment