Iterators and Closures
Iterators and closures are part of the functional programming paradigm
Comprehension
closures
Behavior: a function can be assigned to a variable without being called
Characteristics: hidden from callers, concise, and clear in purpose
The compiler infers which closure trait to use:
Use
FnOncewhen the captured environment is taken by ownership (moved)Use
FnMutwhen the captured environment is borrowed mutablyUse
Fnwhen the captured environment is borrowed immutablyThe environment generally refers to external variables used inside a closure
// common declaration let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; // when there is only a single expression let add_one_v3 = |x| { x + 1 }; // braces can be omitted let add_one_v4 = |x| x + 1 ; // closures infer their type automatically let example_closure = |x| x; let s = example_closure(String::from("hello")); let n = example_closure(5); // the compiler reports an error here // implement lazy loading with a struct, closures, and a trait struct Cacher<T> where T: Fn(u32) -> u32, { calculation: T, value: Option<u32>, } 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 } } } } // force this with the move keyword // this is usually unnecessary; let the compiler infer it // the following code cannot compile because x was moved 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)); }iterators
Iteration and loops have the same performance.
// basic syntax #[test] fn iterator_demonstration() { let v1 = vec![1, 2, 3]; // next modifies the state of the v1 iterator, so mut is required // use into_iter when ownership is needed // use iter_mut when a mutable reference is needed let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None); } let v1: Vec<i32> = vec![1, 2, 3]; // without consuming adaptors (methods that call next), this triggers a compile warning v1.iter().map(|x| x + 1); let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // collect consumes the iterator assert_eq!(v2, vec![2, 3, 4]); /// into_iter example - filter #[derive(PartialEq, Debug)] 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() } // implement a struct that can be iterated struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } } #[test] fn calling_next_directly() { 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); } // use other Iterator trait methods #[test] fn using_other_iterator_trait_methods() { let sum: u32 = Counter::new() .zip(Counter::new().skip(1)) // zip stops pairing when either iterator returns None, so there are only 4 pairs .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum); }
Origin
https://doc.rust-lang.org/book/ch13-00-functional-features.html
…
Closure Type Inference and Annotation
Closures don’t require you to annotate the types of the parameters or the return value like fn functions do. Type annotations are required on functions because they’re part of an explicit interface exposed to your users. Defining this interface rigidly is important for ensuring that everyone agrees on what types of values a function uses and returns. But closures aren’t used in an exposed interface like this: they’re stored in variables and used without naming them and exposing them to users of our library.
Closures are usually short and relevant only within a narrow context rather than in any arbitrary scenario. Within these limited contexts, the compiler is reliably able to infer the types of the parameters and the return type, similar to how it’s able to infer the types of most variables.
Making programmers annotate the types in these small, anonymous functions would be annoying and largely redundant with the information the compiler already has available.
…
Capturing the Environment with Closures
In the workout generator example, we only used closures as inline anonymous functions. However, closures have an additional capability that functions don’t have: they can capture their environment and access variables from the scope in which they’re defined.
Listing 13-12 has an example of a closure stored in the equal_to_x variable that uses the x variable from the closure’s surrounding environment.
Filename: src/main.rs
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
Listing 13-12: Example of a closure that refers to a variable in its enclosing scope
Here, even though x is not one of the parameters of equal_to_x, the equal_to_x closure is allowed to use the x variable that’s defined in the same scope that equal_to_x is defined in.
We can’t do the same with functions; if we try with the following example, our code won’t compile:
Filename: src/main.rs
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool {
z == x
}
let y = 4;
assert!(equal_to_x(y));
}
We get an error:
$ cargo run
Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:5:14
|
5 | z == x
| ^
|
= help: use the `|| { ... }` closure form instead
For more information about this error, try `rustc --explain E0434`.
error: could not compile `equal-to-x` due to previous error
The compiler even reminds us that this only works with closures!
When a closure captures a value from its environment, it uses memory to store the values for use in the closure body. This use of memory is overhead that we don’t want to pay in more common cases where we want to execute code that doesn’t capture its environment. Because functions are never allowed to capture their environment, defining and using functions will never incur this overhead.
Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing mutably, and borrowing immutably. These are encoded in the three Fn traits as follows:
FnOnceconsumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. TheOncepart of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.FnMutcan change the environment because it mutably borrows values.Fnborrows values from the environment immutably.
When you create a closure, Rust infers which trait to use based on how the closure uses the values from the environment. All closures implement FnOnce because they can all be called at least once. Closures that don’t move the captured variables also implement FnMut, and closures that don’t need mutable access to the captured variables also implement Fn. In Listing 13-12, the equal_to_x closure borrows x immutably (so equal_to_x has the Fn trait) because the body of the closure only needs to read the value in x.
When you create a closure, Rust infers which trait to use based on how the closure uses the values from the environment. All closures implement FnOnce because they can all be called at least once. Closures that don’t move the captured variables also implement FnMut, and closures that don’t need mutable access to the captured variables also implement Fn. In Listing 13-12, the equal_to_x closure borrows x immutably (so equal_to_x has the Fn trait) because the body of the closure only needs to read the value in x.
If you want to force the closure to take ownership of the values it uses in the environment, you can use the move keyword before the parameter list. This technique is mostly useful when passing a closure to a new thread to move the data so it’s owned by the new thread.
Note: move closures may still implement Fn or FnMut, even though they capture variables by move. This is because the traits implemented by a closure type are determined by what the closure does with captured values, not how it captures them. The move keyword only specifies the latter.
We’ll have more examples of move closures in Chapter 16 when we talk about concurrency. For now, here’s the code from Listing 13-12 with the move keyword added to the closure definition and using vectors instead of integers, because integers can be copied rather than moved; note that this code will not yet compile.
Filename: src/main.rs
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));
}
We receive the following error:
$ cargo run
Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x)
error[E0382]: borrow of moved value: `x`
--> src/main.rs:6:40
|
2 | let x = vec![1, 2, 3];
| - move occurs because `x` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |
4 | let equal_to_x = move |z| z == x;
| -------- - variable moved due to use in closure
| |
| value moved into closure here
5 |
6 | println!("can't use x here: {:?}", x);
| ^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
error: could not compile `equal-to-x` due to previous error
The x value is moved into the closure when the closure is defined, because we added the move keyword. The closure then has ownership of x, and main isn’t allowed to use x anymore in the println! statement. Removing println! will fix this example.
Most of the time when specifying one of the Fn trait bounds, you can start with Fn and the compiler will tell you if you need FnMut or FnOnce based on what happens in the closure body.