Closure Closure
Closure isan anonymous function that can be assigned to a variable or passed as a parameter to other functions. Unlike functions, it allows capturing the value in the caller scope, for example:
fn main() {<!-- --> let x = 1; let sum = |y| x + y; assert_eq!(3, sum(2)); }
The above code shows a very simple closure sum
that takes an input parameter y
and captures the value of x
in the scope. , so calling sum(2)
means adding 2 (parameter y
) and 1 (x
), and finally returning their sum :3
.
You can see that sum
fits the definition of a closure very well: it can be assigned to a variable, allowing the capture of the value in the caller’s scope.
Rust closures draw lessons from the Smalltalk
and Ruby
languages in form. The biggest difference from functions is that their parameters are in the form of |parm1|
Make a declaration. If there are multiple parameters, just |param1, param2,...|
. The formal definition of closure is given below:
|param1, param2,...| {<!-- --> Statement 1; Statement 2; return expression }
If there is only one return expression, the definition can be simplified to:
|param1| Return expression
The following shows the function and closure implementation of the same function:
fn add_one_v1 (x: u32) -> u32 {<!-- --> x + 1 } let add_one_v2 = |x: u32| -> u32 {<!-- --> x + 1 }; let add_one_v3 = |x| {<!-- --> x + 1 }; let add_one_v4 = |x| x + 1;
Note: The value returned by the last line of expression in the closure is the return value after the closure is executed.
Iterator Iterator
Iterators allow us to iterate a continuous collection, such as arrays, dynamic arrays Vec
, HashMap
, etc. During this process, we only need to care about how the elements in the collection are processed, and There is no need to worry about how to start, how to end, what index to access, etc.
The IntoIterator
trait has an into_iter
method, so we can also explicitly convert an array into an iterator:
let arr = [1, 2, 3]; for v in arr.into_iter() {<!-- --> println!("{}", v); }
The reason why an iterator becomes an iterator is because it implements the Iterator
feature. To implement this feature, the most important thing is to implement the next
method. , this method controls how to get the value from the collection, and the type of the final return value is the associated type Item
.
Example: Simulate implementation of for loop
Because the for
loop is syntactic sugar for iterators, we can completely simulate it through iterators:
let values = vec![1, 2, 3]; {<!-- --> let result = match IntoIterator::into_iter(values) {<!-- --> mut iter => loop {<!-- --> match iter.next() {<!-- --> Some(x) => {<!-- --> println!("{}", x); }, None => break, } }, }; result }
IntoIterator::into_iter
uses a fully qualified method to call the into_iter
method. This calling method is equivalent to values.into_iter()
.
At the same time, we use the loop
loop with the next
method to traverse the elements in the iterator. When the iterator returns None
, break out of the loop.
You can use into_iter
to convert an array into an iterator. In addition, there are iter
and iter_mut
. Their differences are as follows:
into_iter
will take away ownershipiter
is borrowediter_mut
is a mutable borrow
The difference between Iterator and IntoIterator
The two are actually quite easy to confuse, but we just need to remember that Iterator
is the iterator feature. Only when it is implemented can it be called an iterator and next
can be called. >.
And IntoIterator
emphasizes that if a certain type implements this feature, it can become an iterator through methods such as into_iter
and iter
.
Use collect
to collect into a HashMap
collection:
use std::collections::HashMap; fn main() {<!-- --> let names = ["sunface", "sunfei"]; let ages = [18, 18]; let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect(); println!("{:?}",folks); }
zip
is an iterator adapter. Its function is to compress the contents of two iterators together to form a new one like Iterator
The iterator here is an iterator in the form of [(name1, age1), (name2, age2)]
.
Then use collect
to collect the values in the form of (K, V)
in the new iterator into HashMap
. Similarly, The type must be declared explicitly here, and then the internal KV
type of HashMap
can be given to the compiler to deduce, and finally the compiler will deduce HashMap< & amp;str , i32>
, absolutely correct!
Closure as adapter parameter
In the previous map
method, we used closures as parameters of the iterator adapter. Its biggest advantage is not only that it can process elements in the iterator in place, but also that it can capture environment values:
struct Shoe {<!-- --> size: u32, style: String, } fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {<!-- --> shoes.into_iter().filter(|s| s.size == shoe_size).collect() }
filter
is an iterator adapter that filters each value in the iterator. It takes a closure as a parameter, the parameter s
of the closure is the value from the iterator, and then uses s
with the shoe_size
from the external environment > Compare, if equal, keep the s
value in the iterator, if not, remove the s
value from the iterator, and finally pass collect
is collected as Vec
type.
Implement Iterator feature
In the previous content, we have been creating iterators based on arrays. In fact, not only arrays, but also iterators can be created based on other collection types, such as HashMap
. You can also create your own iterators – just implement the Iterator
trait for your custom type.
First, create a counter:
struct Counter {<!-- --> count: u32, } impl Counter {<!-- --> fn new() -> Counter {<!-- --> Counter {<!-- --> count: 0 } } }
We implement an associated function new
for the counter Counter
, which is used to create a new counter instance. Next we continue to implement the Iterator
feature for the counter:
impl Iterator for Counter {<!-- --> type Item = u32; fn next( & amp;mut self) -> Option<Self::Item> {<!-- --> if self.count < 5 {<!-- --> self.count + = 1; Some(self.count) } else {<!-- --> None } } }
First, set the associated type of the feature to u32
. Since the count
field saved by our counter is of type u32
, so in In the next
method, the final returned value is actually the Option
type.
Each time the next
method is called, the counter value will be incremented by one, and then the latest count value will be returned. Once the count is greater than 5, None
will be returned.
Finally, use our new Counter
to iterate:
let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2)); assert_eq!(counter.next(), Some(3)); assert_eq!(counter.next(), Some(4)); assert_eq!(counter.next(), Some(5)); assert_eq!(counter.next(), None);
Other ways to implement the Iterator trait
It can be seen that implementing your own iterator is very simple, but the Iterator
feature has more than just one method: next
, so why do we only need to implement it? Because other methods have default implementations, there is no need to manually implement them like next
, and these default implementation methods are actually implemented based on the next
method.
The following code demonstrates the use of some methods:
let sum: u32 = Counter::new() .zip(Counter::new().skip(1)) .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum);
Where zip
, map
, filter
are iterator adapters:
zip
Merges two iterators into one iterator. Each element in the new iterator is a tuple, consisting of the elements of the previous two iterators. For example, merge iterators of form[1, 2, 3, 4, 5]
and[2, 3, 4, 5]
After that, the new iterator is in the shape of[(1, 2),(2, 3),(3, 4),(4, 5)]
map
converts the values in the iterator into new values [2, 6, 12, 20] after mappingfilter
filters the elements in the iterator. If the closure returnstrue
, the elements [6, 12] will be retained, otherwise they will be eliminated.
And sum
is a consumer adapter that sums all elements in the iterator and ultimately returns a u32
value 18
.