RustArena

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

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

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<_>>().

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