0

Consider the following code:

struct Person {
    name: String,
}

impl Person {
    fn mutate_me(&mut self) {
        // immutable instances fail to be mutated here:
        self.name.push_str("ABCD");
        println!("{}", self.name);
    }
}

impl Drop for Person {
    fn drop(&mut self) {
        // immutable instances are successfully mutated here:
        self.name.push_str("EFGH");
        println!("{}", self.name);
    }
}

fn main() {
    // this instance is not mutable:
    let person = Person {
        name: "John".to_owned(),
    };
    // the following line expectedly fails:
    // person.mutate_me();
    
} // the call to the drop function (which mutates) is successful here

The mutate_me() call fails as person wasn't declared as mut. The drop function, however, is successfully called right before the instance goes out of scope, and successfully mutates the instance. I expect that implementing Drop would need a mutable reference in many (most?) cases. But why can it receive a mutable reference when the instance isn't marked as mut?

Xole82
  • 33
  • 4
  • 1
    Dropping means that a value is moved. Moving a value is a stronger condition than mutating a value. Compare that the following also works: `let a = Person { name: "John" }; let mut b = a; b.name = "EFGH";`. Since `a` is out of scope, whether `a` is mutable does not matter anymore. – SOFe Jan 01 '22 at 07:44

1 Answers1

5

Mutability is a property of the variable not the type, and Drop is an implementation of the type (not to be confused with references/pointers where mutability of the referenced value is part of the type). Whether a variable is mutable or not is mostly for the developer's benefit and does not reflect a technical difference "under the hood".

A value being dropped marks the end of its lifetime meaning nothing afterward can reference the value, so the reference passed to drop is exclusive and therefore mutation is safe. This call to drop is inserted by the compiler which is ultimately making these checks. Drop behavior should not differ whether the last variable that owned the value was mutable or not, that would be confusing and arbitrary.

and perhaps other traits

I don't know of any trait that works like this; only a few have such strong ties to compiler specifics. However, that is irrelevant, you can always mutate an owned immutable value by moving it to a mutable variable:

let person = Person {
    name: "John".to_owned(),
};

let mut person = person;
person.mutate_me();

You can conceptually think of it like this when the compiler calls drop, though its a bit more complicated overall.

See also:

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Thanks for the answer. I think a key takeaway is that I assumed that when a `variable` goes out of scope, `variable.drop()` would successfully be called on that variable. And that didn't make sense. However we don't actually see this call. It happens behind the scenes, and it much easier to understand that its just one part of boilerplate that handles the dropping of variables. Therefore, just like in your example, that boilerplate where this call happens ensures that a mutable borrow occurs. – Xole82 Jan 02 '22 at 05:50
  • @Xole82 Instead of `variable.drop()`, it is in fact [`std::mem::drop(variable)`](https://doc.rust-lang.org/std/mem/fn.drop.html) which if you look at the type, this takes ownership of `variable` rather than a mutable reference. – loganfsmyth Jan 03 '22 at 01:42