Read user input from stdin in Rust

Share this video with your friends

Send Tweet

In this lesson we'll learn how to read user input from stdin, using Rust's std::io module and its Stdin object.

J. Matthew
J. Matthew
~ 5 years ago

I know I'm getting caught up in the technical details here, but I'm curious a) why the ampersand precedes the mut keyword, as opposed to mut &name, and b) why the keyword is necessary at all when passing the argument to read_line. We've already defined name as mutable, so shouldn't a reference to it be sufficient? But when I try read_line(&name), the compiler tells me:

note: expected mutable reference `&mut std::string::String`  
found reference `&std::string::String`

So I get the feeling I'm not quite grasping how exactly mut is working.

Pascal Precht
Pascal Precht(instructor)
~ 5 years ago

Hi Matt,

why the ampersand precedes the mut keyword, as opposed to mut &name As mentioned in your other comments, I'm no compiler expert, so this is just a guess:

The ampersand expresses a reference to something so &name is a reference to the data name in memory. Pretty much everything in rust is an expression like 1+1, some_variable + 1 etc. I believe that, having an expression like mut &name conveys very different semantics from what's the intention here. mut won't have a meaning but could maybe be a variable name and &name is, already, clearly an immutable reference to name.

I could imagine that going with &mut name makes the expression less ambiguous. But again, just a guess. Great question!

why the keyword is necessary at all when passing the argument to read_line. We've already defined name as mutable, so shouldn't a reference to it be sufficient?

As the compiler errors says, passing a &reference to an API that expects a &mut Type cannot work. This is because also reference are by default immutable.

let mut name = "Pascal"; // `name` is mutable
let r = &name; // `r` is immutable`

Now in the case of the code in this lesson it might not be as obvious because we're not creating a variable for the &mut name but instead pass it straight to the API. You could write it like this:

let mut name = String::new();
let r = &mut name;
io::stdin().read_line(r).unwrap();

Does that make sense?

J. Matthew
J. Matthew
~ 5 years ago

Does that make sense?

Thanks, after reading a bunch of articles and thinking about it more, I think I understand this now. We start by declaring name as mutable so that it can be changed by the later code. However, we immediately then pass a reference to name in the next line. As you said, references are immutable by default, and this one is pointing to the state of name at the time the reference was created, when it is an empty string. So that means that the reference is a pointer to an empty buffer on the heap. When the input modifies the string, it could (and in this case certainly would) create a new buffer on the heap to fit the new contents, which means the reference pointing to the old buffer is no longer valid, or at least useful. Therefore the reference itself must mutate, to point to the new buffer, in order for the original variable name to own the new buffer (i.e. the correct data). Yes? Or something like that; I'm still absorbing a lot of new information.

Pascal Precht
Pascal Precht(instructor)
~ 5 years ago

When the input modifies the string, it could (and in this case certainly would) create a new buffer on the heap to fit the new contents, which means the reference pointing to the old buffer is no longer valid, or at least useful. Therefore the reference itself must mutate, to point to the new buffer, in order for the original variable name to own the new buffer (i.e. the correct data). Yes? Or something like that; I'm still absorbing a lot of new information.

That pretty much nailed it.