Collecting a vector from stdin and parsing them as f64 or f32 or i64....etc


#1

Hello. I’ve tried implementing this function in order to deal with stdin from user by letting the user type several values in one line. The idea was to get a vector of values that could be parsed in main as the desired value type. For example:

User is asked for values
User enters: 10. 20. 30.
Resulting vector is Vec = [10.0, 20.0, 30.0]

So, what I wrote is this:

use std::io;
use std::str::FromStr;

fn read_values<T: FromStr>() -> Vec<Result<T, <T as FromStr>::Err>> {
    let mut s = String::new();
    io::stdin().read_line(&mut s).expect("could not read from stdin");
    let a = s.trim().split_whitespace().collect::<Vec<&str>>();
    let mut result = Vec::new();
    for i in a.iter() {
        let u = i.parse();
        result.push(u);
    }
    result
}

fn main() {
    let i = read_values::<f64>().iter().collect::<Vec<f64>>();
}`

When compiling, rust complains that collect::<Vec>() can’t be used like this:
^^^^^^^ a collection of type std::vec::Vec<f64> cannot be built from an iterator over elements of type `&std::result::Result<f64, std::num::ParseFloatError>

My guess is that since the contents of the vector are of type Result, I should probably use something like unwrap(). But I just can’t figure out a way to make this work.
Could you please lend me some guidance?
Thank you in advance
Fabrex


#2

You’re absolutely correct that the issue is that parse() returns a Result. Parsing a string as a float can fail (obviously) and you have to decide how to deal with this failure.

The quick way with unwrap() would be this: let u = i.parse().unwrap(). But I’ve tried it and you’ll quickly run into some more issues with this way.

Luckily, the better way to deal with errors is a lot easier to implement: just return any parsing errors to your caller and let them deal with them!

use std::io;
use std::str::FromStr;

fn read_values<T: FromStr>() -> Result<Vec<T>, T::Err> {
    let mut s = String::new();
    io::stdin().read_line(&mut s).expect("could not read from stdin");
    s.trim().split_whitespace().map(|word| word.parse()).collect()
}


fn main() {
    let i = read_values::<f64>().unwrap();
}

map() applies the inner function (parse()) to each element of the iterator and collect() collects all the elements into a new collection. Usually, collect() requires you to specify the exact kind of collection. But this is one of the precious few cases where Rust can figure everything out on its own. What happens is that after the map(), you have an iterator over Results and Rust can turn that into a Result<Vec<T>, E> by collecting all the Ok calues and stopping at the first Err.

This is a lot to take in, so take your time understanding the code and feel free to ask if you have any further questions. :slight_smile:


#3

You are absolutely right, that is a lot to take in. I guess that means my initial logic for the read_values function was wrong, though I initially wanted to parse each value inside the initial vector (the one I generated from split_whitespace()). In my first attempt, I had this idea of collecting the values from split_whitespace with a vector and then parsing each value with a loop over the elements of that vector. Which, from what I could understand, it’s almost what the map function is doing here, right? Except that the map function simplifies things a lot (as you said). I’m still a bit confused how the (::) value gets passed to parse inside the map function. This is a bit like magic to me.

I still have a lot to learn in Rust.

Thanks for the lesson.

Fabrex


#4

This is type inference in play, which is a bit magical (sometimes annoyingly so). The explicit call there would be map(|word| word.parse::<T>())..., but inference allows not specifying it. Note that you can also call this like map(str::parse).


#5

Yes, as @vitalyd pointed out, type inference allows you just not say a lot of things here. Written a bit more explicitly and with all type parameters specified, this magical line would be:

// Gives an iterator over &str.
let words = s.trim.split_whitespace();
// Gives an iterator over Result<T, T::Err>.
let parsed_words = words.map(str::parse::<T>)
// Unwraps each Result. If it's Ok, it's appended to the
// resulting vector. If it's Err, the vector is discarded
// and the Err is returned. If everything went well, the
// vector is wrapped in an Ok again.
parsed_words.collect::<Result<Vec<T>, T::Err>>()