Skip to content

Guide: Standard input #15534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 15, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/doc/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,112 @@ loop

break/continue

## Standard Input

Getting input from the keyboard is pretty easy, but uses some things
we haven't seen before. Here's a simple program that reads some input,
and then prints it back out:

```{rust,ignore}
fn main() {
println!("Type something!");

let input = std::io::stdin().read_line().ok().expect("Failed to read line");

println!("{}", input);
}
```

Let's go over these chunks, one by one:

```{rust}
std::io::stdin();
```

This calls a function, `stdin()`, that lives inside the `std::io` module. As
you can imagine, everything in `std` is provided by Rust, the 'standard
library.' We'll talk more about the module system later.

Since writing the fully qualified name all the time is annoying, we can use
the `use` statement to import it in:

```{rust}
use std::io::stdin;

stdin();
```

However, it's considered better practice to not import individual functions, but
to import the module, and only use one level of qualification:

```{rust}
use std::io;

io::stdin();
```

Let's update our example to use this style:

```{rust,ignore}
use std::io;

fn main() {
println!("Type something!");

let input = io::stdin().read_line().ok().expect("Failed to read line");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I don't personally regard expect and unwrap as something we should be encouraging for legitimate errors: in my mind they are essentially assertions that the None case never happens, since fail! isn't really recoverable at all.

I would prefer

let input = match io::stdin().read_line() {
    Ok(i) => i,
    Err(e) => {
        println!("Failed to read line: {}", e);
    }
};

Of course, this is more verbose, may introduce too much at a time...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a big call for a bikeshed, and we all ended up with https://github.com/steveklabnik/guessing_game/blob/master/src/guessing_game.rs , which is what I'm building to.

I kinda agree with you, but not being able to read a line from stdin is a pretty exceptional case in this situation, and the entire program is useless in that case, therefore, failing makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like that .expect/.unwrap is the first example of error handling. :(

But... doing it properly is certainly more ugly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, that code actually handles EOF poorly:

$ ./guessing_game 
Guess the number!
Secret number is 34
Please input guess number 1
^Dtask '<main>' failed at 'Failed to read line', /home/rustbuild/src/rust-buildbot/slave/nightly-linux/build/src/libcore/option.rs:246

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Control-D to quit is poor? ;)

But yes, it could be better. The idea here is to just do the basics, not to get it absolutely perfect. There's a balance here, you know?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some improvements in mind here, so let's just merge this for now, and then we can improve in this specific place.


println!("{}", input);
}
```

Next up:

```{rust,ignore}
.read_line()
```

The `read_line()` method can be called on the result of `stdin()` to return
a full line of input. Nice and easy.

```{rust,ignore}
.ok().expect("Failed to read line");
```

Here's the thing: reading a line from standard input could fail. For example,
if this program isn't running in a terminal, but is running as part of a cron
job, or some other context where there's no standard input. So Rust expects us
to handle this case. Given that we plan on always running this program in a
terminal, we use the `ok()` method to tell Rust that we're expecting everything
to be just peachy, and the `expect()` method on that result to give an error
message if our expectation goes wrong.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you're trying to keep it simple until later but I think a few more details here would be helpful. I thought this was puzzling. Too few details and the user will modify something and confuse themselves without recourse.

So Rust expects us to handle this case.

What you mean by this is unclear. Rust expected us to handle it; what if we don't? Will it crash? Will Rust refuse compilation?

Why tell it ok()? Why not just go directly to expect(). If ok() is to tell Rust everything is gonna be fine, when it fails then we lied (it wasn't ok()). How did ok() help?

Also, see below:

// I'll remove the error handling to see what happens...
// Should this compile because some case wasn't handled? Apparently it does...
let input = io::stdin().read_line(); // `Ok(test)` instead of `test`...
let input = io::stdin().read_line().ok(); // `Some(test)`...
// Rereads explanation and is more confused about `Ok` and `Some`

Here's a different type of explanation (hopefully sorta accurate):

Rust will encapsulate the input as protection against possible errors. The user must remove this layer in order to use it. ok() transfers the data from a one wrapper to another wrapper (why? I don't know) so that errors can be handled properly. expect() removes the layer except during errors. During an error, it shows the message it was given.

Not advocating my wording. I just wanted to give a possible example so it wasn't just a complaint.

[EDIT] Moved comment to diff instead of on file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. There's a balance that needs struck here: this stuff requires concepts we don't have yet. I don't want to have to fully explain IoResult, etc. That said, the explanation may not be enough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to explicitly defer it:

// Cheap error handling which is quite complicated and
// will not be explained until Section 7.4
// Basically, use normally and call "Error!" when it errors
.ok().expect("Error!");

Accelerated C++ does that often and I find it really helpful. They never lie, they explain enough for the section, and they tell you where to look if you want more info (will be easier to link when the guides finished though...). Accelerated C++ is quite terse and dry probably though.

Examples:

Its [<< in cout << "Hi"] right operand is a string literal, which has a mysterious type that we shall not even discuss until pg. 176
--- Accelerated C++ pg. 4

The type of a string literal [compared with a character literal] is much more complicated and we shall not explain it until pg. 176.
--- Accelerated C++ pg. 14

The details of [istream] turn out to be complicated enough that we won't discuss it in detail until pg. 222
--- Accelerated C++ pg. 40


We will cover the exact details of how all of this works later in the Guide.
For now, this is all you need.

With long lines like this, Rust gives you some flexibility with the whitespace.
We _could_ write the example like this:

```{rust,ignore}
use std::io;

fn main() {
println!("Type something!");

let input = io::stdin()
.read_line()
.ok()
.expect("Failed to read line");

println!("{}", input);
}
```

Sometimes, this makes things more readable. Sometimes, less. Use your judgement
here.

That's all you need to get basic input from the standard input! It's not too
complicated, but there are a number of small parts.

## Guessing Game: complete

At this point, you have successfully built the Guessing Game! Congratulations!
Expand Down