Why to use "mut" twice here?

i'm reading the book and on the guessing game chapter, why does "mut" have to be used twice? any particular reason or just convention?

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

1 Like

The first mut is a property of binding (a name, to say simply). It means "the following code is allowed to do something that will mutate guess".

The second mut is actually &mut - i.e. it's taking an exclusive borrow of the value behind this binding; it is necessary in order to actually modify the data.

11 Likes

There are two kinds of references: mutable and immutable references. The mut marker just allows the creation of mutable references, it doesn't make all references to the variable mutable. Writing &guess creates an immutable reference, which cannot be used to modify guess even if its marked mut.

This feature is useful because you may have multiple uses of guess, and only some of them modify it. By having two kinds of references, you can tell whether each call will modify it, because the ones that modify it say &mut guess, and the others say &guess.

8 Likes

I checked the reference to read more about the different uses.

The first mut is part of an IdentifierPattern, which is part of the let statement. Interesting side note, I didn't realize you can use the ref keyword there too, which actually allows you to omit the second &mut:

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    // When we add `ref` here, then
    // `guess` is *not* of type `String`
    // but of type `&mut String`:
    let ref mut guess: String = String::new();
    
    // We can verify that:
    let _: &mut String = guess; // works
    //let _: String = guess; // fails

    io::stdin()
        .read_line(guess) // no borrow operator needed here
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

(Playground)

Note that the above example isn't very idiomatic though. I don't remember having ever seen let ref mut.

The second &mut in your example is a borrow operator. You can use it to go from a value of type T to a value of type &mut T (or to go from a value of type String to a value of type &mut String in your example).

1 Like

The others have answered in the terms of the language, I would try to answer on different level.

Rust is based around the idea that shared mutable state is bad. It's, basically bad in principle (have you ever tried to draw picture on the same sheet of paper with a friend? it leads to mess: someone needs to draw and other guy or guys may just give hints), but Rust raises it to the language level.

To that end every place where mutability is allowed it marked specially.

That's why we have two muts here: second one says that read_line wants to mutate a line and first one is needed to have a mutable object which can be mutated in the first place!

If you know C or C++ then you know that const is viral there. That's because C/C++ are built on the idea that mutability is the norm, but you may want some const islands for safety. Rust is just the opposite: immutability is the norm, but you may want to mutate some things. That means mut is viral here.

8 Likes

That's, basically, because it allows you to write read_line(guess) — and that one doesn't look like it would mutate anything.

Mutability in Rust is a big deal and it's usually unidiomatic to hide it. But yes, certain constructs allow you to do that.

1 Like

It should be noted that mutability is not a property of the "object" or value, but of the binding. Thus:

use std::io;

fn do_the_work(mut guess_buffer: String) {
    // here there is another binding `guess_buffer`, which allows mutation
    io::stdin()
        .read_line(&mut guess_buffer)
        .expect("Failed to read line");

    println!("You guessed: {guess_buffer}");
}

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let guess = String::new(); // the immutability is part of the binding
    do_the_work(guess); // we pass the `String` by value
}

(Playground)

Even if we omit the mut in let guess, we can still mutate the String (not through the binding guess, but through a later binding guess_buffer, which is declared to be mutable).

I hope this didn't cause more confusion than being helpful.

5 Likes

i'm thinking that once a variable has been declared, that's it. why keep telling that it is mutable?

fwiw, declaring variables at the top should've been enough. plus easier to maintain that way. only one place.

Because even if something is allowed to modify it for a while, that doesn't mean that you don't want to share it read-only between a bunch of different things later. So when you pass it to something, you can say "well yes, I know that I'm allowed to modify that, but I'm passing it to that other thing and I don't want that other thing to modify it".

5 Likes

The second time you use mut, you are taking a mutable borrow of your mutable variable. You could also have taken a shared (read-only) borrow of it. You can also mutate a mutable variable directly without a borrow at all. These cases handle different situations and all are necessary. Read The Book, especially the parts about ownership and borrowing. Rust's handling of variables do not work like variables in most languages, and this may be the language's single greatest feature.

5 Likes

Nothing is being declared twice. The &mut variable_name is not a declaration. It's an expression that takes a mutable borrow (reference) of the variable.

Again, &mut variable_name doesn't say that the variable is mutable; it says that the reference to it allows mutation. A non-mut reference to a mutable variable wouldn't allow mutation.

If you think about it, this is basically the only useful way of denoting this, because if references automatically inherited the mutability of the binding they are created from, then there would simply be no way to create multiple shared references to mutable bindings. Every reference to such a binding would then be mutable, so it would automatically enforce uniqueness, even if it were an unnecessary restriction.


As a side note, your first assumption shouldn't be that there are such fundamental design errors in Rust. Rust is an exceptionally well-designed language, and there are very good reasons behind why it works the way it does. Instead of accusing the language of being unnecessarily verbose or restrictive, try to think about what would happen if a particular design decision were reverted. Usually, the results wouldn't be pretty.

4 Likes

not negatively accusing a language design just trying to wrap my head into the Rust way. i have to beat it any which way to see its limits.

1 Like

Even if it's sometimes hidden, Rust differs between values (of a certain type T), shared references (to a value of that type T), and mutable references (to a value of that type T).

The std::io::Stdin::read_line method does not retrieve a String but a mutable reference to a String. So passing guess there would be a type error.

Distinguishing between passing by-value or passing shared or mutable references helps keeping track of which part of the program may modify which data. Ultimately, this is very helpful for concurrency to avoid situations where one thread modifies data that is accessed by another. But even in the single-threaded case, this can have advantages. Consider the following example:

#[derive(Copy, Clone, Debug)]
enum Fruit {
    Apple,
    Pear,
    Banana,
}
use Fruit::*;

fn get_last_element1(list: &[Fruit]) -> Fruit {
    if list.len() > 0 {
        list[list.len() - 1]
    } else {
        panic!("empty list of fruits; can't get last element")
    }
}

fn get_last_element2(list: &mut Vec<Fruit>) -> Fruit {
    list.pop()
        .expect("empty list of fruits; can't get last element")
}

fn main() {
    // The following `mut` makes us aware that the list may change,
    // e.g. it may be different for Block A and Block C:
    let mut list = vec![Apple, Pear, Banana];
    // Let's try to hold a reference to the last element:
    let last_fruit: &Fruit = &list[list.len() - 2];
    {
        // Block A
        // Passing a shared reference here means that the list cannot
        // be modified, i.e. the full list will still be the full list.
        println!("Last element: {:?}", get_last_element1(&list));
        println!("Full list: {:?}", list);
    }
    println!("---");
    {
        // Block B
        // Passing a mutable reference here means that we must expect
        // the list to be changed.
        println!("Last element: {:?}", get_last_element2(&mut list));
        println!("Full list: {:?}", list);
    }
    println!("---");
    {
        // Block C
        // The list has been changed by Block B already.
        println!("Last element: {:?}", get_last_element1(&list));
        println!("Full list: {:?}", list);
    }
    println!("---");
    // What should `last_fruit` refer to? The banana has already been
    // removed from the list.
    println!("Inspecting reference: {:?}", last_fruit);
}

(Playground)

get_last_element1 and get_last_element2 are two fundamentally different functions. The first one returns a copy of the last element without modifying the list, and the second removes the last element from the list. By passing references of two different types helps making this difference clear. Thus you can't accidentally write get_last_element2(&list) instead of get_last_element1(&list).

Requiring bindings to be declared with mut (such as in let mut list = vec![Apple, Pear, Banana];) helps making clear that we are allowed to create mutable references to the list (and that what is list in this scope can change throughout this scope).

Let's compare this with an example in Python:

#!/usr/bin/env python3

def get_last_element1(fruits):
    return fruits[-1]

def get_last_element2(fruits):
    return fruits.pop()

fruitsA = ("Apple", "Pear", "Banana") # non-mutable tuple
get_last_element1(fruitsA)
#get_last_element2(fruitsA) # this would cause an error

fruitsB = ["Apple", "Pear", "Banana"] # mutable list
print(get_last_element1(fruitsB)) # does this modify `fruitlist`?
print(get_last_element2(fruitsB)) # does this modify `fruitlist`?

Python3 allows us to create a list of fruits as an immutable tuple fruitsA or mutable list fruitsB. Thus, if we write get_last_element2(fruitsA), we will get a runtime error, because get_last_element2 will try to modify the tuple (which doesn't work).

But when we later write these two lines:

print(get_last_element1(fruitsB)) # does this modify `fruitlist`?
print(get_last_element2(fruitsB)) # does this modify `fruitlist`?

Then it's very hard to tell which of these two lines actually modify fruitsB here.


Another example in Python:

#!/usr/bin/env python3

fruitsalads = [["Apple", "Pear"], ["Apple", "Banana"]]

# Let's try to make fruitsalads immutable:
fruitsalads = tuple(fruitsalads)

#fruitsalads.pop() # this would cause an error now

fruitsalads[0].pop() # but we can still mutate the contained first list
print(fruitsalads)

This will print:

(['Apple'], ['Apple', 'Banana'])

Despite our attempt to make fruitsalads immutable.

In Rust, we'd be protected from these problems, because if we only have a shared reference to the outer Vec, we cannot use that to get a mutable reference to the inner Vec:

fn try_to_modify(vec: &Vec<Vec<i32>>) {
    vec[0].pop();
}

fn main() {
    let mut vec_of_vecs = vec![vec![1, 2], vec![3, 4]];
    vec_of_vecs.push(vec![5, 6]);
    try_to_modify(&vec_of_vecs);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `*vec` as mutable, as it is behind a `&` reference
 --> src/main.rs:2:5
  |
1 | fn try_to_modify(vec: &Vec<Vec<i32>>) {
  |                       -------------- help: consider changing this to be a mutable reference: `&mut Vec<Vec<i32>>`
2 |     vec[0].pop();
  |     ^^^ `vec` is a `&` reference, so the data it refers to cannot be borrowed as mutable

For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground` due to previous error


So Rust distinguishing between all these cases really serves a purpose.

2 Likes

To my simple mind it goes like this (see the comments):

    // There is a string called "guess" that may be modified
    let mut guess = String::new();

    // The read_line function can modify my string. 
    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    // This function, whatever it is, is borrowing "guess" but can't modify it.  
    some_func(&guess);

    // At this point I can be sure that "guess" is the same as it was after I read it.
    //...

That last point is very significant. It tells me that I don't have to worry about whatever "some_func" does in order to understand this section of code. How cool is that!

So we see the first "mut" tells us a thing can be modified. The subsequent "mut", or lack of, tell us who might be modifying the thing.

Of course this is not just convention, the program will not compile without the "mut" in place.

It's easier for the compiler but not for developer. Note how we started with languages which required declarations on the top (FORTRAN, Pascal, C and so on) and switched to languages which allowed declarations in the place where variable is initialized (C++, Java, C and so on).

Note that C is in both lists and that not a mistake: it started as language where you had to declare variables on top and then added the ability to declare them elsewhere.

Why do you think it have done that?

Because if you want to understand how the program works you have to know all the places where variable is mutated. And most variables, even if technically mutable, are modified in a few places.

3 Likes

That's actually pretty good attitude. I think you should try to understand that these mut marks are parts of something which makes Rust the most loved language.

That's because Rust does something right which almost all other language do wrong.

Forget about programming for a moment.

Would you want to live a world where every time you give a courier a package you have to ask person on the other end to check if content is not tampered with? That's “normal”, imperative programming for you. Where once a variable has been declared, that's it. And where everyone who have access may freely mutate everything mutable.

Or, alternatively, would you want to live in the world where you can not open the package and change it content after you received it, but have to copy it's content into some other package and alter it that way? That's functional programming for you.

Rust, finally, does things “like layman does it”. Even if I lend you my excercise book with notes taken in class by default I expect to get it back intact. If you really want to alter my notes — you can make a copy and change that. But teacher can add notes and marks freely because it has my permission to do so.

And that's how Rust works: by default things are passed around without attached implicit permission to modify them (like it would be done in most imperative languages). But where you want to allow modification — you give that permit explicitly.

Of course Rust have ways to relax that control. When you would reach the place where Arc/Rc/Mutex types are discussed you would find out about more complicated protocols. But, again, it's similar to what happens in real life, too. Remember this story:

Who has the patch pumpkin?

To explain: David Croy once told me that at a previous job, there was one tape drive and multiple systems that used it for backups. But instead of some high-tech exclusion software, they used a low-tech method to prevent multiple simultaneous backups: a stuffed pumpkin. No one was allowed to make backups unless they had the "backup pumpkin".

In real world we tend to ask for a permission to use or consume something but are more lax when we just need to look on it… and Rust, finally, moves that practice into realm of programming languages.

P.S. Sure, there are also nice mathematical properties of that decision, too, you can read about them in works of new assistant professor but it's something like structured programming: if you grew up on TI-BASIC you may really struggle with all these new-fanged nested ifs, fors and whiles and would long for “simplicity” of Goto — yet once you realize that nested ifs, fors and whiles allow you to think locally about your program you would never want to go back. Similarly Rust approach to mutability makes it possible to think locally about your program. Once you would realize how large of a burden lack of that capability placed on you in a “normal” imperative language you would never want to go back (functional languages are different, though: some features they allow you to do can not be easily replicated in Rust thus some people still long for Haskell when they deal with Rust).

5 Likes

This is something that I did not understand initially, and took a long time to understand. Good thing that you clearly explain it here!

One thing that helped me with it is that because of how moves are checked you can't tell if someone else modifies something you gave away.

I might have considered the vector as immutable, but in order be allowed to give you ownership of it, I had to not have any more active borrows of it. And that means that whether or not you change it, I don't have any (legal) way of knowing.

And thus making this a property of the binding, not of the value itself, fits better with the whole idea of ownership.

(I certainly didn't want to tear down my house, but if I sell it someone else who wants to tear it down and build a new one, that's their business.)

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.