Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to put a type annotation in an iterator's collect statement?

Tags:

rust

I have this code:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    file.lines().map(|x| x.unwrap()).collect();
}

fn main() {
    let data = load_file();
    println!("DATA: {}", data[0]);
}

When I try to compile it, I get this error:

error[E0283]: type annotations required: cannot resolve `_: std::iter::FromIterator<std::string::String>`
 --> src/main.rs:6:38
  |
6 |     file.lines().map(|x| x.unwrap()).collect();
  |                                      ^^^^^^^

In fact, if I change the load_file function in this way, the code compiles smoothly:

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
    return lines;
}

This solution is not "Rusty" enough because ending a function with a return is not encouraged.

Is there a way to put the type annotation directly into the file.lines().map(|x| x.unwrap()).collect(); statement?

like image 666
Davide Aversa Avatar asked Jan 12 '15 19:01

Davide Aversa


2 Answers

Iterator::collect's signature looks like this:

fn collect<B>(self) -> B
where
    B: FromIterator<Self::Item>, 

In your case, you need to tell it what B is. To specify the types of a generic function, you use syntax called the turbofish, which looks like func::<T, U, ...>()

Your load_file function should look like this:

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    file.lines().map(|x| x.unwrap()).collect::<Vec<String>>()
}

You can also allow some type inference to continue by specifying some types as the placeholder _:

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    file.lines().map(|x| x.unwrap()).collect::<Vec<_>>()
}
like image 55
GGalizzi Avatar answered Oct 07 '22 00:10

GGalizzi


In fact your problem was slightly less noticeable. This does not compile (your initial piece of code):

use std::fs::File;
use std::io::{BufRead, BufReader};

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    file.lines().map(|x| x.unwrap()).collect();
}

fn main() {
    let data = load_file();
    println!("DATA: {}", data[0]);
}

But this does:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn load_file() -> Vec<String> {
    let file = BufReader::new(File::open("foo.txt").unwrap());
    file.lines().map(|x| x.unwrap()).collect()
}

fn main() {
    let data = load_file();
    println!("DATA: {}", data[0]);
}

Can you notice the subtle difference? It's just a semicolon in the last line of load_file().

Type inference in Rust is strong enough not to need an annotation here. Your problem was in that you was ignoring the result of collect()! The semicolon acted like a "barrier" for the type inference, because with it collect()'s return type and load_file()'s return type are not connected. The error message is somewhat misleading, however; it seems that this phase of type checking ran earlier than the check for return types (which would rightly fail because () is not compatible with Vec<String>).

like image 21
Vladimir Matveev Avatar answered Oct 06 '22 23:10

Vladimir Matveev