Step 6 of 7
collect, the turbofish, and collecting Results
collect() can build almost any collection. It works out which one from the type you ask for, and there are two ways to ask:
let v: Vec<i32> = it.collect(); // annotate the variable
let v = it.collect::<Vec<i32>>(); // or annotate the method call
let v: Vec<_> = it.collect(); // `_`: "work out the item type yourself"
The ::<...> syntax is affectionately called the turbofish. You need it where there's no variable to annotate: in the middle of a chain, as an argument, or for parse::<i32>() and sum::<u32>().
use std::collections::{BTreeMap, HashMap, HashSet};
fn main() {
let names = ["ann", "bo", "ann", "cy"];
let upper = names.iter().map(|n| n.to_uppercase()).collect::<Vec<_>>();
let unique: HashSet<&str> = names.iter().copied().collect();
let initials: String = names.iter().filter_map(|n| n.chars().next()).collect();
let lengths: BTreeMap<&str, usize> = names.iter().map(|n| (*n, n.len())).collect();
let ids: HashMap<u32, &str> = (1..).zip(names.iter().copied()).collect();
println!("{:?}", upper);
println!("{} unique", unique.len());
println!("{initials}");
println!("{:?}", lengths);
println!("id 3 is {}", ids[&3]);
}
["ANN", "BO", "ANN", "CY"]
3 unique
abac
{"ann": 3, "bo": 2, "cy": 2}
id 3 is ann
- A
HashSetdrops duplicates. AStringcan be collected fromchars (or from&strs andStrings). - Maps are collected from
(key, value)tuples. A later duplicate key overwrites the earlier one.BTreeMapkeeps its keys sorted, so it's safe to print; aHashMap's order is unpredictable, so here we only look keys up. filter_map(f)isfilterandmapin one:freturns anOption;Nones are dropped and the values inside theSomes are kept. (n.chars().next()isNonefor an empty string.)
Collecting Results: stop at the first error
This is one of Rust's most useful tricks. When every item is a Result, you can collect into Result<Vec<T>, E>: you get Ok(vec) if every item was Ok, or the first Err, and collecting stops right there.
use std::num::ParseIntError;
fn parse_all(input: &str) -> Result<Vec<i32>, ParseIntError> {
input.split(',').map(|s| s.trim().parse::<i32>()).collect()
}
fn main() {
println!("{:?}", parse_all("4, 8, 15"));
println!("{:?}", parse_all("4, eight, 15"));
// or skip the bad ones: .ok() turns a Result into an Option
let good: Vec<i32> = "4, eight, 15".split(',').filter_map(|s| s.trim().parse().ok()).collect();
println!("{:?}", good);
}
Ok([4, 8, 15])
Err(ParseIntError { kind: InvalidDigit })
[4, 15]
It combines beautifully with ?: let nums: Vec<i32> = line.split(',').map(|s| s.parse()).collect::<Result<_, _>>()?;. The same works with Option: collecting Option<T> items into Option<Vec<T>> gives None if any item was None.
Which one should you pick? Fail (collect into Result) when bad data means the whole input is wrong, like a config file. Skip (filter_map with .ok()) when bad entries are expected and harmless, like noise in a log; at work, count or log what you skipped.
Common mistakes
collectwith no type to aim for:
fn main() {
let words = "3 1 2";
let nums = words.split(' ').map(|w| w.parse::<i32>().unwrap()).collect();
println!("{:?}", nums);
}
error[E0283]: type annotations needed
Write let nums: Vec<i32> = ... or .collect::<Vec<_>>().
- Collecting references into a collection of values.
.iter()yields&i32, and aVec<i32>can't be built from references:
fn main() {
let v: Vec<i32> = vec![1, 2, 3];
let copy: Vec<i32> = v.iter().collect();
println!("{:?}", copy);
}
error[E0277]: a value of type `Vec<i32>` cannot be built from an iterator over elements of type `&i32`
Add .copied() (for Copy items) or .cloned() (for things like String) before collect, or collect a Vec<&i32> if references are what you want.
Your task
Write fn parse_scores(line: &str) -> Result<Vec<u32>, ParseIntError>. The line holds scores separated by whitespace. Return all of them, or the first parse error. An empty line gives Ok with an empty vector. Use a single chain that collects into the Result, no loops.
Challenge 2
A config lists ports separated by commas, but some entries are junk. Write fn valid_ports(list: &str) -> Vec<u16> that returns every entry that parses as a u16 (after trimming spaces), in order, and skips the rest. "80, 443, http, 70000, 8080" gives [80, 443, 8080] (70000 is too big for a u16). Use filter_map, no loops.
Challenge 3
Write fn index_users(users: Vec<User>) -> HashMap<u32, User> that moves the users into a map keyed by their id, so they can be looked up quickly. If two users share an id, the later one wins. Use one chain ending in collect, no loops.
Previous: Consumers: sum, count, min, max, fold, any, find Next: Implementing Iterator for your own type