Implementation of a direct way to read floats and ints from the terminal


#1

For reading integers what we do now is something like:

let mut line = String::new();
// Not handling the result (Ok, Err)
io::stdin().read_line(&mut line).expect("Failed to read line");
let mynum: i16 = line.trim().parse().expect("Invalid input!");
// Ignoring the Ok, Err again

Update: Why can’t we have something like this:

let mut x: i16;
io::stdin().read_int(&mut x).expect("Failed to read integer");

Our test suite (something we’re implementing in rust) processes a large dataset and then keeps asking the user for inputs and without implementing a separate macro_rules! there’s no way to directly input it. Now isn’t there a way we can directly input the integer. If we can’t then why not? What implementation prevents us from doing so?


#2

<devils-advocate>

To do this, you need buffering so you can look ahead. But that means allocating a global, shared buffer. And all access to stdin has to go through it, or you’ll end up dropping input. That imposes a perf and memory cost on everyone whether or not they use it. Yuck. As it happens, stdin is already globally buffered.

But let’s say that gets added, and it only gives you the ability to read integers. What’s so special about integers? What about floats, and bools? What about user-defined types like integer newtypes or NaN-free floats? Surely users should be able to support this?

But Rust has nothing appropriate defined for such general-purpose parsing. The existing FromStr machinery is wholly insufficient. So now you’re having to bolt a parsing system on to the standard library and… it’s just simpler to push this to an external crate like anything else in Rust that doesn’t need to be in the standard library.

If you’re doing this frequently, either use an existing crate, or define your own.

</devils-advocate>


#3

Just a note: this post is more suited for the users’ forum than here.


#4

If we were to improve ergonomics here, I reckon that wouldn’t be a great interface, for two reasons:

  1. many specialized functions, one for every type; each of them
  2. taking an out parameter instead of returning the value, which makes it necessary to always use a temporary variable.

A better API would be something generic, with a more general trait bound that enables Readability, akin to FromStr just with inputs where there isn’t one single string, but a potentially infinite stream.

trait FromStream {
    type Err: Error;

    fn from_stream<R: Read>(r: &mut R) -> Result<Self, Self::Err>;
}

trait Read {
    …
    fn read_and_parse<T: FromStream>(&mut self) -> Result<T, T::Err> {
        …
    }
}

Then all types that can be parsed from a string could also implement FromStream, presumably with minimal code changes and/or maximal sharing between the FromStream and the FromStr implementation.


#5

What does it meant to “read” an int from a stream? Unfortunately, there seem to be a lot of options (mostly variations on the following):

  • Interpret bytes as platform endian int.
  • Interpret bytes as UTF-8, parse string as int.

For FromStr, the latter is obviously correct. For byte streams, this seems less obvious.


#6

Good catch. I had been presuming only the UTF-8 alternative, since the OP’s query said “terminal”.


#7

As a note: Read is not enough. You need a way to do lookahead. For scan-rules, I ended up just exposing a &str for input. I’m not sure there’s a better way to do this, given that every alternative involves putting a buffer somewhere.


#8

Yeah, whatever. I didn’t think through all the requirements for the implementation – my comment was more a general note about the API, but you’re right. Could be Read + Seek even, or something along those lines.


#9

But that means allocating a global, shared buffer. And all access to stdin has to go through it, or you’ll end up dropping input.

I’m confused. I thought that was exactly what stdin already does (it contains a BufReader), and exactly the reason why.


#10

I am not sure what you mean by “there’s no way to directly input it.” Based on your suggestion I guess you are possibly coming from a C++ background? Is perhaps this what you wish you could write?

15
6 7 8

as opposed to

15
6
7
8

?

If that’s what you want, try out this utility. It can be used like this:

let mut words = Words::new(io::stdin());

let word = words.next().expect("Unexpected end of stream");
let mynum_1: i16 = word.parse().expect("Invalid input!");

let word = words.next().expect("Unexpected end of stream");
let mynum_2: i16 = word.parse().expect("Invalid input!");

#11

I stand corrected.