Conflation of mutability and reassignment with 'mut' keyword

If I want to call .next() on an iterator, I need to have a mutable reference to the iterator:

let input = "myteststring";
let mut chars = input.chars();

But now chars can be reassigned to something else, which isn't what I wanted. Is there a way to resolve this? I don't understand why mutable references have been conflated with reassignment.

With &mut T you can reassign the T behind it. You can't avoid reassigning while allowing to take unique reference of it.

1 Like

Sorry, I don't understand what you mean, probably because I'm brand new to rust. Would you be able to provide an example of what you mean?

Imagine we have some special keyword mut_ref_only which only allows to take mutable reference(unique reference) but not allows direct reassignment into it.

let mut_ref_only i = 42;
*(&mut i) = 0;

But you can still reassign to the variable with only taking &mut of it.

1 Like

I don't understand where &mut comes into it (amongst other things, like understanding how &mut actually works). Would you be able to relate this back to my original example?

Is there a reason why the theoretical mut_ref_only couldn't work? Why wouldn't the compiler be able to enforce this?

You can think of mut as being contagious. If I want to mutate something deep within an object then every object above it needs to be mutable. So in self.some.vec.push(42), not only does the vec field need to be mutable, but so does self and some.

But how do we introduce that initial mutability? In Rust it is done by making the variable at the very top mut or being given something behind an &mut reference (where the caller would have had to make their variable mut).

The reason it wouldn't work is because mut_ref_only is logically unsound.

Say we changed the language's semantics so there is some marker to say "you can't reassign this variable, but you are still allowed to mutate it" (i.e. mut_ref_only).

What I can do is take a mutable reference to that variable (which is valid) then copy a new value into that place using the dereference operator.

// Let's say we have two variables
let a: u32 = 42;
// This one can't be reassigned, but you can take a &mut reference to it
let mut_ref_only b: u32 = 0;

// Make that reference
let some_reference_to_b: &mut u32 = &mut b;
// then overwrite the old value for "b" with "a"
*some_reference_to_b = a;

// That was just a verbose way of writing
b = a;

So just by allowing me to take an &mut reference to some variable I've been able to reassign that variable to something else. But hang on, mut_ref_only was meant to explicitly prevent this!

That contradiction means our hypothetical "you can only take a &mut reference to a variable and can't reassign it" rule is logically inconsistent, hence why it can't work.

3 Likes

Here's an example of this.

struct Thing(String);
impl Thing {
    fn do_specific_stuff(&mut self) {
    }
}

fn replaces_using_mutual_reference(thing: &mut Thing) {
    let _ = std::mem::replace(thing, Thing("bar".to_string()));
}

fn main() {
    // I want to do the specific stuff, but I want to avoid reassignment
    // like `thing = other_thing;`
    let mut thing = Thing("foo".to_string());
    thing.do_specific_stuff();
    
    // But if I can `do_specific_stuff`, I can do anything else that uses
    // a mutual reference to myself...
    replaces_using_mutual_reference(&mut thing);
    
    // Oops, I've basically been reassigned
    println!("{}", thing.0);
}

Well that's kind of my point. Why doesn't it prevent it? What's stopping the compiler from enforcing that?

I suppose I'm actually more interested in the corollary. Is there a way to allow reassignment without the need for the variable to hold a mutable reference to the data? If not, why wouldn't such a thing be feasible?

Does this count?

fn reassign(mut x: &u32) {
  if rand() {
    const OTHER_VALUE: u32 = 42;
    x = &OTHER_VALUE;
  }

  // do something with x
}

Here I'm allowed to reassign x (i.e. point it at something else) but can't actually mutate the thing it refers to.

What about an example where the variable is declared in the same scope as the reassignment, rather than passed in as a parameter?

It would be completely arbitrary for one.

#[derive(Default)]
struct Foo {
    a: u32,
    b: String,
    c: Option<usize>,
}

fn main() {
    let mut foo = Foo::default();
    let fooref = &mut foo;
    fooref.a = 7; // I'm not replacing foo, it's fine
    fooref.b = "ok".to_string(); // I'm not replacing foo, it's fine
    fooref.c = Some(1); // I'm not replacing foo, it's fine
    // Oops, I've effectively replaced foo
}

I probably just don't understand what you're getting at, but... sure:

let mut foo = "foo".to_string();
foo = "bar".to_string();
// Not a `&mut` in sight
1 Like

Yes, I think I haven't made myself clear at all. I'm new at this and don't have all of the necessary language to ask all of the right questions. I do know enough to know that your "foo".to_string() example is not at all what I'm asking for though.

The best example I can think of to explain my point is how const works in Javascript. All const does is guarantee that the value cannot be reassigned. The internal mutability of the value being assigned is not relevant. If the value is supposed to be an immutable data structure, it's the sole responsibility of that data structure to use const itself.

So what I'm wondering is kind of the inverse of what const does. I'm trying to keep track of a value in a variable, and I don't want to mutate the value at all and do not care if it is mutable or immutable. I do want to reassign the value of this variable, and refer to it in various places so that they all have the "latest" value I'm tracking.

Right now, the only way to achieve this is to declare that a variable with mut, which also requires that the value it holds is mutable, which (as @Michael-F-Bryan mentioned) is contagious in that it requires everything all the way down to be mutable. But I don't care about those values being mutable and have zero intention of ever attempting to mutate them, I just want to reassign the value in a variable.

No, it doesn't require anything. In Rust, values ans types don't have mutability; bindings ("variables") do. If let xyz = expression; compiles, then let mut xyz = expression; is also guaranteed to compile; you don't have to have all references be mutable in the right-hand side.

That sounds like you want to reference the same value from multiple places at the same time, but still be able to change it without invalidating the other references. That's exactly what internal mutability enables, so it is relevant :slight_smile: . However, it also typically has a cost -- you need to make sure no one is reading your structure while it is being written to -- so Rust makes you explicitly opt-in to the functionality by wrapping your type in the appropriate locking (or otherwise mutable-access checking) data structures.

For example, you might end up with an Arc<Mutex<YourType>> . There are other possibilities with various trade-offs, to serve different needs. (Threaded or multi-threaded, capabilities of the type, need to obtain references or just copies of entire types, etc.)

More in the Book.

2 Likes

chars is not a reference to the iterator. It is the iterator itself. I think this is where the confusion lies.

4 Likes

It's been said before, but that is logically unsound. In other words, there isn't anything that any compiler or there, now or in the future, can do about it.

No. In fact at this point it should be obvious that changing some value in e.g. a struct (using a &mut binding) and changing the value of the struct itself (using a mut binding) are in a fundamental sense the same operation. The real difference is only in how much data is altered: only a field in the former, and all fields (and the container value itself) in the latter.

I'd argue that that is simply the latter case in my paragraph above, only applied to values that are immutable borrows. So nothing fundamentally special there, and thus of course it counts.

2 Likes

In a sense this could be managed with something like

let mut v: Rc<T> = p.clone();

or even with something like

let mut v = &data;

Either would allow you to reassign v to a new value, but not to modify the value that is pointed to. Of course, they also prevent anyone else from modifying it.

For references, you can independently specify whether the reference can be reassigned, and whether the referred-to value can be modified.

Here, x can be reassigned, but *x cannot be modified:

    let a = 5;
    let b = 6;

    let mut x = &a;
    x = &b; // reassigning is OK
    // *x = 7; // compile error: modification is not OK

Here, x cannot be reassigned, but *x can be modified:

    let mut a = 5;
    let mut b = 6;

    let x = &mut a;
    // x = &mut b; // compile error: reassigning is not OK
    *x = 7; // modification is OK

The difference with Javascript is that in Javascript variables always store references, while in Rust you don't need the extra indirection. In your original code there are no references, so there is only one level of mutability.

2 Likes

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.