Understanding why non copyable values can't be moved out of a mutable reference

After typing this out I think its obvious to me why this is not aloud but posting it anyway.

TL;DR: In the following examples allowing a value to be moved out (not copied) of the mutable reference to the struct could allow several undesirable things from invalid memory no longer pointing to the correct place to memory leaks -- I believe anyway. Copying obviously solves the problem so values with a copy trait would just work but may not be what you want with larger data structures..

In this example I have a simple struct with one field on called value with a type of Box<i32>. I chose Box in this example because it recreates the issue.

These examples are contrived to highlight the issue not indicative of the actual problem I ran into - not looking for how to solve the issue but rather what specifically made it fail to compile and the rationale behind it.

The first example

// Simple struct who stores a heap allocated value
struct Store {
  value: Box<i32>
}

impl Store {
  // Very contrived function to demonstrate quetsion
  // stores the new value returning the old box to the caller
  //
  // Failes to copmile and the root of this question - why?
  fn set_value(&mut self, new_value: i32) -> Box<i32> {
    // Can't move box on the mutable reference
    //
    // My assumption is that self.value is effectively now invalid memory
    // until it is assigned in the next statement which while I know that
    // it's not gauranteed safe. 
    let old = self.value;

    // For example if I didn't set the value and instead did a return like so
    // 
    // return Box::new(new_value);
    // 
    // then the struct has invliad memory upon returning to the caller which is clearly
    // not good and I believe this is why borrow checker is calling it out. 

    // reassiging immediatley here
    self.value = Box::new(new_value);
  
    // would like to return the box ("pointer") here
    return old;
  }
}

fn main() {
  
  let mut s = Store {
    // Initial value
    value: Box::new(15)
  };

  // Trying to set a new value on the struct and get back the 
  // point to the heap allocated value
  let old_value = s.set_value(30);

  println!("{:?}", old_value);
  println!("{:?}", s.value);
}

This yields the following error:

   |
   |     let old = self.value;
   |               ^^^^^^^^^^
   |               |
   |               cannot move out of borrowed content
   |               help: consider borrowing here: `&self.value`

Shorter example with using just structs but same general pattern:

#[derive(Debug)]
struct Person {
  age: i32
}

struct Store {
  value: Box<Person>
}

impl Store {
  fn set_value(&mut self, new_value: Person) -> Box<Person> {
    let old = self.value;

    self.value = Box::new(new_value);
  
    return old;
  }
}

fn main() {
  let mut s = Store {
    value: Box::new(Person{ age: 15 })
  };

  let old_value = s.set_value(Person{ age: 30 });

  println!("{:?}", s.value);
  println!("{:?}", old_value);
}

and yields the same compiler error

  --> main.rs:12:15
   |
   |     let old = self.value;
   |               ^^^^^^^^^^
   |               |
   |               cannot move out of borrowed content
   |               help: consider borrowing here: `&self.value`

As I reason through this I think the answer is obvious why this is not allowed. If the value could be moved from self in set_value then upon return the variable s could potentially have invalid memory that should not be trusted on the field value because there is no guarantee that it will be set in that function.

My understanding anyway and could imagine there is a whole host of other potentially very bad things that could happen. Any expansion on this would be appreciated or correction to my understanding.

2 Likes

Your understanding is correct. References are just a dumb pointer at runtime, and cannot change the value's lifetime. When the value's lifetime is end, its destructor will be called if exist. So the value should be stay in valid state until to be dropped.

2 Likes

Also note that, because what matters is that the referenced memory remains in a valid state, you can move out of a mutable reference if you put another valid value in place of the moved value using std::mem::replace().

3 Likes

Thank you! I had ran across std::mem::replace and didn't fully grok at the time. So it is possible if needed.

For posterity, here is the first example rewritten to demonstrate the use of std::mem::replace mentioned above by @jameseb7.

use std::mem;

struct Store {
  value: Box<i32>
}

impl Store {
  fn set_value(&mut self, new_value: i32) -> Box<i32> {
    mem::replace(&mut self.value, Box::new(new_value))
  }
}

fn main() {
  
  let mut s = Store {
    value: Box::new(15)
  };

  let old_value = s.set_value(30);

  println!("{:?}", old_value);
  println!("{:?}", s.value);
}

After looking back at the docs its obvious how that solves this issue if this behavior is needed.

1 Like

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.