20

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Davide Aversa
  • 5,628
  • 6
  • 28
  • 40

2 Answers2

36

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<_>>()
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
GGalizzi
  • 813
  • 1
  • 10
  • 18
  • Thank you for your answer. Also if this is not the "correct" solution to my specific problem. I was really looking for the syntax for specifying type annotation in function calls. This will be useful in the future for sure. ;) – Davide Aversa Jan 13 '15 at 00:18
17

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296