Why is it so difficult to get user input in Rust?

I think comparing the error handling cases is the right thing to do. Because in an interactive program, ignoring failures (which cin >> does by default) is always the wrong thing. Okay, it's fine in toy programs, maybe, but for anything even remotely serious? You can't just turn anything that isn't a digit into 0 and silently continue. Worse, if the user happens to accidentally type something like 1- instead of 10, like I did just now while testing, the - stays in the input stream to screw with any input you might want to get in the future. Except when it's a whitespace character; those just vanish, because it's 1985 and everybody's writing space-separated formats.

cin >> is optimized to make the worst behavior (turning decoding errors into junk data) the easiest thing to do. And it has this extra thing where it does arbitrary and inconsistent things with leading or trailing characters. In fact, I think it's impossible to use cin responsibly for interactive programs because its behavior with respect to whitespace is just too weird; you should always use readline instead. This not only makes your interactive program act more or less like every other interactive program, but also makes it vastly easier to print useful error messages like '1-' is not an integer.

Now, if you're writing a program that takes structured input (CSV, JSON, etc.) and you're parsing it? Maybe you have a case for using >>, but you still need to check the failure state after every decoding that can fail, because otherwise you'll treat 10.1 as two or even three tokens instead of one, and that will trash your whole state machine. So you better be really religious with setting the delimiters appropriately and checking the flags after every operation. You'll probably be better off writing your own lexer and processing a stream of tagged tokens instead of raw text, unless, of course, you can just plug in an already-written CSV or JSON parser which bypasses the whole problem.

And if you take the above very sensible advice and apply it to C++, hey, it's actually just as easy or slightly easier to do all this stuff correctly in Rust. It's only the wrong stuff, the things you should almost never do, that's easier in C++.

Python, on the other hand, is a whole different story. Because it actually does the right thing: input returns a string, and when you try to convert one to an int, it raises a ValueError containing the problematic string. Python fails hard and it fails fast. Which is just exactly what you want a lot of the time, for prototypes and one-offs -- "bail out" is the most conservative thing you can do, and if there's some other way to handle it, you can always catch the error and continue.

This post is getting way longer than I wanted it to be. I was going to also say something about how Python's approach is fine and Rust's approach is also fine, but they serve different purposes. However, all I really want to point out with this post is that you have the right idea when you render the error-checking version in C++, because unless you're writing toy, fizzbuzz-esque programs, you absolutely must have something beyond cin >> x.

31 Likes