Mutable borrow from immutable borrow

In the following example the function bar(self) borrows self immutably. However, I found out, I could create a mutable borrow, which would then allow me to change its field (example below).

Why it is allowed to make a mutable borrow from self?
IMO, if the function required mutation, the function signature should say so. I do understand that it doesn't matter in this case, as self is consumed by the function.

I am confused, because "immutability" is more strict than "mutability" and this looks like something of a privilege escalation. The function signature communicates that self will not be changed (before being dropped) - but it does.

#[derive(Debug)]
struct Foo(u32);

impl Foo {
    fn bar(self) {
        // self.0 = 12; // <-- "Error: cannot assign"

        let mut self2 = self; // <-- Why allow  `mut` here?
        self2.0 = 12;

        println!("self2: {:?}", self2);
    }
}

fn main() {
    let a = Foo(42);
    println!("a = {:?}", a);

    a.bar();
}

(Playground)

Output:

a = Foo(42)
self2: Foo(12)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.66s
     Running `target/debug/playground`

The reason this came up was, that I tried to solve an exercism.io task and was presented a function declaration with an immutable borrow. I thus assumed (barring errors in the question) this should be solved without mutating.

self is not special here, it is always possible to move a immutable variable to a mutable one (given no other constraints)

fn main() {
    let x = [0u8; 10];
    // x[0] = 3; // cannot assign
    let mut y = x;
    y[0] = 3;
}

this works in your case too because self is moved in and not a borrow

1 Like

This function does not borrow self at all - as you've noted yourself, self is consumed, not borrowed. Then, for the function's code, it doesn't differ from owned local variable: both can be operated in any way, since there is no one to require them back.

2 Likes

I see, I am using the wrong vocabulary. We always only talk about a "borrow", when references are involved. Otherwise (like in the examples above) we say a variable is "mutable/immutable consumed"?

Ah. I understand that self is just another variable.

Why can we create a mutably owned binding from an immutable one? In my mental model this looks like acquiring write privileges to a variable that was formally intended as read-only.

That's because the part of the local variable which says that it's mutable or immutable is just part of its name, whereas in a reference the immutable/mutable (shared/unique) part of the reference is indeed part of its type and cannot be circumvented without opening the can of worms that UB is with mem::transmute unless you have some kind of interior mutability.

I can say:

let mut x = 2;
let x = x;

But I cannot say

let x: &() = &();
let x: &mut () = x;

Because I cannot "upgrade" the borrowing like that. I could go the other way around, but that's implicit, anything you have write access to, you'd logically have read access to as well.

Basically once you transfer ownership, you don't care anymore what happens with it. So you transfer ownership into the bar method, which then can do whatever it wants with it again, including mutating it.

1 Like

See this blog post (of mine) about mut in Rust, and how it differs w.r.t. other languages: https://danielhenrymantilla.github.io/posts/2019-02-24-mutation-part-2-to-mut-or-not-to-mut/

Then what is the difference between:

fn bar(mut obj: Foo) { 
    obj.0 = 12;
}
fn bar(obj: Foo) { 
    let mut obj = obj;
    obj.0 = 12;
}

?
Is the former just syntactic sugar for the latter?

To add to that, I like to formulate the issue like this: in Rust, there are no "mutable" or "immutable" types. Mutability is the property of a specific value or, even more precisely, the binding (e.g. variable or pattern) with which you access the value.

When we talk about &T and &mut T, it is not the reference itself that is mutable or immutable. It is a conceptual distinction about whether by dereferencing the reference, you are allowed to mutate the pointed value or not. You can thus think about the dereferenced value of a reference as a "binding", too. (This is analogous to the "lvalue/rvalue" vocabulary used in most C-like languages.)

4 Likes