Why does type inference work only without the question mark?

This function works:

fn convert(filepath: String) -> Result<String, Box<dyn Error>> {
    let f = File::open(filepath)?;
    let mut reader = csv::Reader::from_reader(f);
    let mut records: Result<Vec<_>, _> = reader.records().collect();
    // records?.reverse();
    for record in records {
        println!("{:#?}", record);
    }
    Ok(String::from(""))
}

But I couldn't figure out how to use reverse() when record is a Result (uncommenting that line gives an error because apparently records?.reverse() moves records), so I thought, I can just change

let mut records: Result<Vec<_>, _> = reader.records().collect();

to

let mut records: Vec<_> = reader.records().collect()?;

which should be completely equivalent, right?

Nope...

error[E0282]: type annotations needed for `std::vec::Vec<_>`
  --> src\main.rs:28:19
   |
26 |     let mut records: Vec<_> = reader.records().collect()?;
   |         ----------- consider giving `records` the explicit type `std::vec::Vec<_>`, with the type parameters specified
27 |     // records?.reverse();
28 |     for record in records {
   |                   ^^^^^^^ cannot infer type

I usually just shadow the result.

fn convert(filepath: String) -> Result<String, Box<dyn Error>> {
    let f = File::open(filepath)?;
    let mut reader = csv::Reader::from_reader(f);
    let records: Result<Vec<_>, _> = reader.records().collect();
    let mut records = records?;
    records.reverse();
    for record in records {
        println!("{:#?}", record);
    }
    Ok(String::from(""))
}

I think the issue is the ? calls .into() on the error (so the error type is unknown), and the .collect has several types it can collect into. And the type inference can't resolve both at once, but I'm not certain.

You could also use turbo fish on the collect, if you tell it the error type.

fn convert(filepath: String) -> Result<String, Box<dyn Error>> {
    let f = File::open(filepath)?;
    let mut reader = csv::Reader::from_reader(f);
    let mut records = reader.records().collect::<Result<Vec<_>, csv::Error>>()?;
    for record in records {
        println!("{:#?}", record);
    }
    Ok(String::from(""))
}
4 Likes

Because there are too many unknowns:

  • collect uses FromIterator which you can ask to make you any type,
  • ? uses Try trait internally, which could theoretically work with any type,
  • and then ? uses From on the error type to convert from any type.

Type inference gets lost when you have conversion from something to anything, and anything to something. It doesn't want to choose that "anything" part.

If you want a 1-liner, you can use turbofish syntax:

.collect::<Result<Vec<_>, _>>();
2 Likes