How to add a crate?

i'm playing with Rust I/O and came across something weird.

fn main(){
   let mut line = String::new();
   println!("Enter your name :");
   let b1 = std::io::stdin().read_line(&mut line).unwrap();
   println!("Hello , {}", line);
   println!("no of bytes read , {}", b1);
}

the code is supposed to ask for user input. i was surprised that is how you would wait for user input. to me, it's convuluted. hard to maintain. i was told that instead, a proper way to get something like in c++ is to put it in a crate?

in c++, to ask for user input is simply

cin>> user_input;

it's "elegant" in that it is easy to maintain code that way.

string user_input;
cin >> user_input;

and

let mut user_input = String::new();
std::io::stdin().read_line(&mut user_input).unwrap();

are equivalent.

1 Like

No, they aren't – see below for more.

Yeah, and it's completely, totally, wrong.

That "one-liner" in C++ suffers from the following problems:

  1. It behaves in an unexpected way. It is supposed to read into whatever data type user_input has, but it's not as simple as that. For instance, it's entirely inappropriate for reading strings, because it only reads until the first whitespace character and leaves the rest in the input buffer, which is counter-intuitive behavior.
  2. It's an Undefined Behavior footgun. When the read fails, the expression on the right-hand side isn't updated. This means that if you had an uninitialized variable and you think you read into it, you might have not, and you can in fact sometimes read an uninitialized value, triggering UB.
  3. It leaves zero opportunities for error checking in the expression itself, and leaves error handling to the stream, worse yet, it does so in a stateful manner. Basically, you have to check the eof and good flags of the stream, and this isn't reflected in the value of the variable you are reading into, so it's trivial to forget and/or be completely oblivious of the fact that error checking needs to be done at all.
  4. It conflates advancing the stream with parsing. If the error doesn't come from the stream, but from the parsing phase (e.g. integer over- or underflow), then you will get a value which is in the range of the valid values of the type, and you will never know an error happened, unless you explicitly check for the stream, once again.

So no, the C++ one-liner you proposed is absolutely not maintainable by any reasonable definition. It's quite the opposite, it's full of surprises, hard to use correctly, and easy to refactor/change incorrectly.

The correct way to read user input in C++ would be to read a line as a string, and then parse it in different ways depending on its type. For example, an integer might need to be read with something like:

int read_int() {
    std::string line;
    if (!std::getline(std::cin, line)) {
        throw std::runtime_error("failed to read from standard input");
    }
    
    std::size_t pos = 0;
    int value = std::stoi(line, &pos);
    
    if (pos != line.size()) {
        throw std::runtime_error("garbage after input");
    }
    
    return value;
}

This is neither shorter, simpler, nor more "maintainable" than the Rust equivalent.

In contrast, the presented Rust code doesn't let you get away with any of this sloppiness, because every step that can fail (reading from the stream, converting the string to another type, etc.) explicitly returns a Result, so you can't get a value out of them unless they succeeded, and thus you are forced to handle all potential error cases. You also can't get an uninitialized variable or nonsensical values. And actually, the Rust version is shorter and simpler than the equivalent correct C++ code.

20 Likes

Since this thread doesn’t contain a Rust code version of code that includes doing integer parsing, let’s be explicit about this. I suppose, you had something like this in mind? (Unless you thought of using some Result<…> return type and ? operator…)

fn read_int() -> i32 {
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();
    line.trim_end().parse().unwrap()
}

Although there’s probably a difference to your C++ code with regards to handling whitespace at the end of the line then…

By the way, since I did wonder myself what the exact behavior of cin >> with a string in C++ was, I tried to Google search for some documentation for a few minutes but didn’t find anything good and detailed. Where can one actually find information such as the following?

Is there any decent online C++ standard library documentation? How to find the entry of how cin’s >> operator implementation works?

Right, I just couldn't tell what exact type user_input was supposed to be, so unless it's really always just a string, I erred on the safe side by assuming that it needed further parsing.

Yes, but unfortunately, there's a lot of misinformation and incorrect unofficial documentation, and people who don't know about the quality of such pieces of documentation tend to pick one randomly and recommend it, and thus a lot of bad documentation got perpetuated (e.g. cplusplus.com).

One site I found to contain at least overwhelmingly correct information is cppreference.com, and the behavior of istream >> string can be found e.g. here.

One fairly confusing thing is that you'd think that an input operator is going to be documented in the description of basic_istream; alas, that's not the case. The istream::operator>> members included in the stream's own documentation only cover numeric overloads.

This is, I believe, due to the "anything goes" approach to overloading in C++, which allows operator>> to be defined for an arbitrary RHS while still keeping the istream type on the LHS. This is largely what Rust's coherency rules forbid, and it illustrates one of the many problems with the approach pretty nicely. So yeah, it can be pretty hard or at least frustrating to discover good, or any, documentation for the standard library.

Oh, and of course, there is the C++ standards document itself! Unfortunately, it's paid and it's not cheap, and I honestly can't imagine why anyone would want to pay on the order of 100$ just to find out what stream >> string is supposed to do.

3 Likes

Nobody uses it, anyway, don't worry. C++ world is weird is some sense, because standard is incredibly important, yet, in the same time, no one actually reads it.

This seeming contradiction comes from the ISO requirement that standard can not be made available for free.

The end result: everyone including users of the language and developers of the compilers use freely-available drafts and not the actual C++ standard. I'm 99% sure if draft would say one thing and actual standard would say something different no one would even bother with following the standard. It's simply not useful.

And yes, draft explains everything you need to know about operator >> is a very detailed form.

4 Likes

yes but the c++ code is easier to read therefore to maintain.

man, you don't have a Rust book published?

so, if there is "println!()" for output, is there an opposite macro to properly read user input?

I believe not, and likely this doesn't require a macro, since unlike formatted output, there's no such thing as formatted input – you have to parse.

If you have a type that can readily be parsed from a string, then I suggest you either search for a relevant, small crate, or you can copy-paste the following simple but artfully-crafted function:

use core::str::FromStr;
use std::error::Error;
use std::io;

fn read_stdin<T>() -> io::Result<T>
where
    T: FromStr,
    T::Err: Error + Send + Sync + 'static,
{
    let mut line = String::new();
    io::stdin().read_line(&mut line)?;
    line.parse().map_err(|e| {
        io::Error::new(io::ErrorKind::Other, e)
    })
}
3 Likes

The C++ code is not easier to read IMO. It doesn't make clear that it stops reading at any whitespace.

6 Likes

Maintenance of code depends on many factors, besides readability. And readability is certainly not a synonym for shortness, which is quite evident if you have ever read monad-heavy Haskell code that doesn't use do notation. After many years of writing bugs, and then discovering Rust, I learned to appreciate explicitness in code, and hope you eventually will too.

8 Likes

For what it's worth (my personal anecdote), I got a C++ book back in the early aughts, when I was a teenager interested in programming. I picked up the Borland C++ IDE and dutifully typed in a few basic, output only exercises. They always opened in a new terminal that closed when the program execution stopped, so I quickly learned to put cin.get() or some variation at the end of each program so I could verify the output.

I felt empowered by the code I'd written, and wanted to make a simple text based game, something simple like the guess the number game that the official rust book uses as its example.

But I ran into a hard stop with cin >> guess;. Turns out there's some kind of internal buffer that the get call sees. I tried my best google-fu to figure out how to flush that buffer after each input, but I never figured it out.

Now this is a bit unfair to C++:

  • I was a teenager, completely new to programming
  • I was on windows and didn't know what a terminal was, much less how to use one
  • There's almost certainly a better way to do what I wanted to, and relying on printed books for programming knowledge doesn't work well
  • I never learned better, since my college taught Java in programming 101

But it's illustrative of the points made above: cin is a bit of a footgun, and Rust forces you to acknowledge that failures can happen.

2 Likes

Although i highly disagree with what you said about C++ being more readable and maintanable, it is very verbose to simply read user input in rust (to be fair, reading user input isn't easy, rust just makes this explicit, as it does with almost everything).

there are crates for it, if you'd bothered to look you'd find them: text_io

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.