Functional Language Features: Iterators and Closures1
To run the program:
$ cargo run --bin functional-features
Compiling cli-program v0.1.0 ...
This section covered elements of functional programming in Rust, including:
- Closures, a function-like construct you can store in a variable
- Iterators, a way of processing a series of elements
/// One example, creating a closure that in turn calls self.most_stocked.
fn give_away(&self, preference: Option<Color>) -> Color {
preference.unwrap_or_else(|| self.most_stocked())
}
Compare closures to functions:
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
And borrowing / borrowing for mutation:
let only_borrows = || println!("From closure: {:?}", list);
let mut borrows_mutably = || list.push(7);
To define what happens to captured variables, we use Fn
traits:
FnOnce
: Moves captured variables out of the body only implement this trait.- All closures implement at least this trait.
FnMut
: Closures that don't move captured values, but might mutate captures.- Can be called more than once.
Fn
: Don't move captured values, and don't mutate captured values.- Can be called more than once.
In Rust, iterators are lazy:
// No effect! In fact, this will trigger a warning "unused_must_use".
vec![1, 2, 3].iter();
To combine with closures, for example:
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()
}
Knowing what I know now, the following improvements could be made to chapter 12:
- let query = args[1].clone();
- let path = args[2].clone();
+ struct Config {
+ query: String,
+ path: String,
+ }
+
+ impl Config {
+ fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
+ args.next();
+
+ let query = match args.next() {
+ Some(arg) => arg,
+ None => return Err("Didn't get a query string")
+ };
+
+ // ...
+
+ Ok(Config {query, path})
+ }
+ }
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
- let mut results = Vec::new();
-
- for line in contents.lines() {
- if line.contains(query) {
- results.push(line);
- }
- }
-
- results
+ contents.lines().filter(|line| line.contains(query)).collect()
}
Apparently, iterators are a zero-cost abstraction in Rust, or ~0 overhead.
NOTE: See https://doc.rust-lang.org/book/ch13-04-performance.html.