How to read an integer from stdin

Hello everyone, yesterday I started studying rust because I really like the proposal of this technology, for now I'm in the guessing_game part of the book, after doing these exercises I tried to explore the language a little bit, I tried to make a user input and after that I made a mathematical operation, I did it based on the knowledge of C ++, I would like to know what the error was, moreover, I didn't quite understand the part of the input in rust, while in C ++ a simple cin << solve, in rust it tells me to do a lot of instructions, it is anyway? forgiveness for such a great text, and thank you very much if you can help me.
my code:

use std::io;

fn main() {

let mut x = string::new();

io::stdin()

    .read_line(&mut x)

    .expect("Failed to read line");

let y=5;z=23;

let mut result;

result = (y*x)/x;

println! ("the result is: {}",result)

}

You need to parse the input string into a number in order to perform arithmetic on it. Something like this should work (untested):

let mut input_line = String::new();
io::stdin()
    .read_line(&mut input_line)
    .expect("Failed to read line");
let x: i32 = input_line.trim().parse().expect("Input not an integer");
let y=5;
let z=23;
let result = (y*x)/x;
println! ("the result is: {}",result);

wow, do you really need all this for an input?

Using only the standard library, yes you’ll probably need these steps. The input_line buffer can be re-used though, and the .expect calls are error handling (there’s “shorter” alternatives such as using the ? operator in a function returning some fitting Result type). So it’s basically just

  • make sure to have a buffer (only need to do this one time)
  • make sure you have some handle to stdin (only really needed one time as well; unless you need to access stdin from different parts of your program (or even different threads), you might want to call .lock() on std::io::stdin()’s returned handle and save the locked handle in a variable to avoid the need to (implicitly) re-lock on every read operation)
  • call read_line on stdin with the buffer to read a line into the string
  • call trim if you want to ignore extra whitespace around the input and call parse to turn the string in to an integer type (or anything that supports FromStr)

Of course things can be made shorter using external crates. Using dependencies in Rust is luckily really easy. You might want to try to use something like this crate if you want to type less:

text_io - Rust (might be worth looking into the examples on github for this one since the documentation isn’t perfect for many of the macros)

or maybe you like this crate better

scan_fmt - Rust

(there’s probably even more alternatives)

4 Likes

I hope you are not about to become "RustHater35" on some other language forum.

One of the beauties of Rust is that it is a high performance, low level language. Which means that "out of the box" using only the standard library one might have to write more code than when using the likes of Python,

On the other hand, people have been creating thousands of libraries, "crates", that implement al lot of that Python like, high level, functionality. As steffahn suggests above for example.

The upshot of all this, at least for me, is that a lot of ones Rust code looks quite a lot like high level creations in Python, Javascript etc.

Admittedly it can be a challenge to find a suitable crate to do what you want. I guess the best, most widely used, will become common knowledge in the Rust user community over time.

Well, just look at what this does, step by step:

io::stdin() // the rough equivalent of `std::cin`
    .read_line(&mut input_line) // actually read the line
    .expect("Failed to read line"); // which can fail, however
let x: i32 = input_line
    .trim() // ignore whitespace around input
    .parse() // convert to integers
    .expect("Input not an integer"); // which, again, can fail

All of these steps (reading a line, handling errors if I/O fails, converting to an integer, handling invalid input) are pretty much inevitable; the only thing I could consider optional is trimming leading/trailing whitespace. (I might even be wrong about that; I don't remember whether read_line leaves the trailing newline in the buffer.)

Anyway, to see why these are not pointless steps, consider what happens in C++ when you read an integer using std::cin >> x;

  • If the input doesn't contain anything, you get an uninitialized variable, silently. If you use the variable in its uninitialized state, you just ran into Undefined Behavior.
  • If the input is invalid (e.g. not a number or too big), you silently get a 0 or INT_MAX, respectively. So you have no way of knowing whether the conversion succeeded, because errors look just like success.
  • Some overloads of operator>> work funnily in how they handle leading/trailing whitespace. Some ignore/skip it, some treat it as an error, etc.

For those reasons, consider how you would need to read an integer in C++ correctly, robustly, and reliably:

#include <iostream>
#include <string>

int main() {
    std::string line;
    if (std::getline(std::cin, line)) {
        int x = std::stoi(line);
        // you can use `x` here
    } else {
        std::cout << "Failed to read\n";
    }
}

The code above also attempts to read a line, then checks if reading succeeded, then parses the line, and throws an exception if it does not represent a valid integer.

You can see that this code is not exactly any simpler or shorter than the Rust version. The difference between the two languages is that in C++, you are allowed to get away with the version that's wrong and effectively a UB-magnet, while in Rust, you are forced to do the right thing. Rust's error handling is also more consistent. The C++ code doesn't throw an exception if reading from stdin itself fails, but throws if the string is not a valid integer. In contrast, fallible Rust functions always return Result, allowing you to handle errors uniformly.

So, all in all, while the C++ code looks "easier", if you do things properly, it really isn't. What C++ hides from you is not some particularly smart computation or logic that Rust is incapable of – instead, it just hides two footguns in a manner that you don't even know you were supposed to do something with the errors.

14 Likes

Here's a previous thread on the same topic, with discussion of the tradeoffs involved:

2 Likes

LOL, when I went down to learn about rust it was by more experienced friends in the area to talk about its power, unlike haskell which was a mandatory subject in college, rust seems to me to be a really incredible project and with a wonderful community, my only concern is whether I will be able to mastering this language completely, however, I will work hard, and I know that when I need help I can count on you.

Excellent.

I guess you don't mean me specifically. I have been Rusting for over a year and for sure I have only scratched the surface of the language.

Too busy getting Rust code into production.That is probably not a wise way to proceed. My code is very likely suboptimal, overly verbose and non-idiomatic Rust. But so what? I rest easy knowing that Rust has not allowed me to make a total disaster with surprises waiting to call me in the night. Which has borne out to be true over tis last year and a half.

A fact that I as reminded of today as I struggled to fix up some C++. But that is another story...

Whether you like it or not, some Haskell knowledge might actually help you with learning Rust, for example generics (like Haskell’s polymorphism), and in particular with traits (which are very much like Haskell’s type classes; probably the main reason for the listing of Haskell in the “influenced by” on Rust’s Wikipedia page). I myself like Haskell quite a lot, probably also because my first exposure to Haskell came precisely in this style:

3 Likes

Wow, this one is really silent. It is scary that every C++ tutorial I have ever seen has UB in it.

3 Likes

Yup. The sad truth is, C and C++ are perhaps the two most inappropriately-taught languages among the ones which can be considered mainstream. Seriously, in my personal experience, the majority, perhaps 80% of people that I encountered claiming that they "know" C or C++ do not in fact know either language well. And I don't mean that they are unable to write functioning code or that they struggle with algorithmic thinking. Rather, I mean that they often don't know about the enormous amount of subtle or not so subtle bugs one can introduce by simply following the path of least resistance in these languages.

There's a good amount of misconceptions, bad practices, bad habits, bad teaching, and other problems in the culture. While C and C++ both are standardized, most C and C++ programmers do not write code against the standard. They instead write code that they think "should obviously be correct", based on a simplistic, often seriously misguided mental model of hardware – which the formal specification of the languages (called the Abstract Machine) freqently collides with, and does so in important aspects such as validity of memory and pointers.

The myriads of bad tutorials, low-quality teaching material, and clueless university professors (seriously!) only exacerbate the problem, which I think is simply too deeply-rooted to be salvageable at this point. For example, have you ever used cplusplus.com as a learning resource? It's no good, many of their examples suggest code that actually or potentially leads to Undefined Behavior. Nevertheless, I see this site recommended to basically every programmer who innocently asks for a first C or C++ learning resource.

So, be careful when writing simple-looking code in C or C++. There are a good number of bear traps in those languages, and you have to basically know a whole bunch of exceptions, special cases, and recognize particularly dangerous patterns to be productive in terms of avoiding bugs while still being able to write code reasonably fast.

4 Likes

To be fair, I don't really like Haskell myself, either. It was never taught to me, and I wasn't forced to learn it. I picked it up out of curiosity. However, I simply find it too clunky and inconsistent of a language to be enjoyable. (E.g. I can't stand the conventionally whitespace-sensitive syntax that requires me to put delimiters in visually unpleasant places, or the weird and seemingly arbitrary constraints on type classes which somehow still don't prevent them from being incoherent.)

Still, I have found my Haskell experience massively useful for learning Rust, and I would recommend anyone trying to get the hang of the language to go and learn themselves some Haskell, because several central concepts can be transferred from Haskell to Rust, and I have generally found Haskell learning resources more numerous and easier to find.

By the way, to clarify my earlier post.

Even though I probably also would recommend people to try learning some Haskell for the sake of improving their understanding of Rust’s type system, in this case in this sentence

I was actually just trying to say the knowledge of Haskell that OP already has from their college class might end up helping them with learning Rust.

1 Like

Yup, that was clear – I was just letting the pressure out by describing what I didn't like about Haskell :sweat_smile:

1 Like

I see. Let me add my personal opinion then (which might be a bit off-topic, sorry for that.)

In my opinion Rust has significant advantages over Haskell with its trait implementation coherence story and with stability guarantees in general, with avoiding/disallowing non-exhaustive pattern matches and avoiding panics/exceptions in general, especially the way the standard library is structured (Haskell has a lot of API that throws exceptions), and with thread safety. [I’m only listing things here that Haskell could more-or-less easily also have. Of course Rust also can do lower-level stuff, doesn’t need GC, has move semantics, etc…]

On the other hand Haskell has some clear advantages in its effect system (I really miss pure functions and explicit IO type, etc. in Rust), and in the fact that polymorphic code doesn’t have to be monomorphized (allowing things like polymorphic recursion); and I guess I’ll also have to mention tail-call-optimization. I guess that adding some form of polymorphic recursion (for boxed types) and tail call optimization to Rust is still possible in the future. For pure functions the ship might have sailed, I can’t see how a proper effects system would be retrofitted to the language. I’ve come across multiple instances of the need to enforce deterministic/side-effect-free implementations of traits such as Hash or Eq for some unsafe code in Rust to be sound; it’s a bit heart-breaking that this will probably never get proper language support. The only workaround left then involves lots of extra unsafe traits with lots of documentation.

It’s just a matter of taste, but I do prefer Haskell syntax. The worst part of Rust syntax IMO is the use of < and > for type parameters and the fact that they have to be explicitly introduced everywhere (because there’s no enforced lowercase/uppercase rules and also type variables are conventionally uppercase in Rust anyways). “<>” are such a pain in editors (missing support to find matching >s, because they aren’t actually brackets), macros (if < … > was a single token tree, some macro definitions would be much easier), and also syntax ambiguities and their consequences (yay turbofish!).

2 Likes

Interesting. My observation is that it has been common practice in C, C++ and many other languages for decades to omit any error checking in example codes in tutorials and books. The obvious motivation being to remove "clutter" so that the hapless beginner can see the tree for the woods, as it were. To make the idea under discussion clear.

This is quite understandable. Unfortunately it leads to said beginners "graduating" whilst being totally oblivious of the billion things that can go wrong. Handling errors is some kind of fluff that only gets thought about deeply after years of experience and thousands of hours of debugging.

We also see this in Rust tutorials. Error checking is kept from obscuring the code under discussion by copious use of .unwrap or .expect.

The major difference being that when ones .unwrap laden code fails one is almost certain to see where and why very easily and quickly. Very soon, handling errors appropriately is front and centre of ones mind.

I have always been bemused by this obsession with "the happy path", where your pristine algorithm is supposed to go, unsullied by error situations that are swept under the rug. Exceptions being the epitome of this.

In my programming world the "sad path" is normal. Pretty much a main consideration in embedded, safety critical and security related code. It should be faced front and centre. Which I am happy to find Rust does.

3 Likes

Hm, yes, so this might be all personal at this point, because that's another thing that really bugs me in Haskell. I don't see the practical value of it - I just don't remember ever making the mistake of accidentally performing I/O in a function where I shouldn't have, and I would be really annoyed if I weren't able to "println!()"-debug my code. (And Haskell's unsafePerformIO is not exactly reliable – of course, that's why it's unsafe.)

Well, Vim's Rust mode does correctly identify matching angle brackets for me, so that's probably an editor problem. And while I see some people annoyed by the turbofish, it never really bugged me, it's just two characters that I got used to.

By the way, that‘s possible in Haskell. Debug traceing isn’t really considered a side-effect.

Looking up the link for this one reminded me of the fact that type-signature-based search in rustdoc is still severely lacking.

Thanks, that's useful. And yes, searching for type signatures would be a fine addition to rustdoc, I use it in Hoogle all the time. That's a feature of the Haskell ecosystem that I also love.

1 Like