Search code examples
rustmove-semanticsmutable

How do I implement move semantics on a struct with a mutable field?


I have a minimal example of code that implements move semantics for some container:

use std::mem;

impl<'a, T: 'a + ?Sized> Drop for Cointainer<'a, T> {
    fn drop(&mut self) {}
}

struct Cointainer<'a, T: 'a + ?Sized> {
    item: &'a /* mut */ T,
}

impl<'a, T> Cointainer<'a, T> {
    fn mv(self) -> Cointainer<'a, T> {
        let new = Cointainer { item: /* &mut */ self.item };
        mem::forget(self);
        new
    }
}

fn main() {}

That compiles and works without any problems.

I realized I will need to mutate the value referenced by Cointainer::item, so I've made the reference mut. When I do so, I get:

error[E0505]: cannot move out of `self` because it is borrowed
  --> src/main.rs:14:21
   |
13 |         let new = Cointainer { item: /* &mut */ self.item };
   |                                                 --------- borrow of `*self.item` occurs here
14 |         mem::forget(self);
   |                     ^^^^ move out of `self` occurs here

I need to create a new container and transfer ownership of item there, and drop the old one.

This example is artificial. The actual "move" operation does some other stuff, and doesn't necessarily return the same container type.


Solution

  • The rules of references state:

    1. At any given time, you can have either but not both of:

      • One mutable reference.
      • Any number of immutable references.

    Your code with immutable references works because those can be freely copied. Your code with mutable references fails because, as far as the compiler can tell, you would need to have have two concurrent mutable references: the saved reference in new and then mem::forget might also need it.

    As humans, we recognize that mem::forget will not access the guts of our structure. This is what unsafe code is for: when the compiler cannot guarantee the code we have is truly safe.

    A small unsafe block and some casting to a raw pointer and back solves the problem. As with any unsafe code, it should have a big comment block explaining why the compiler doesn't understand it and why it's truly safe.

    impl<'a, T: 'a + ?Sized> Drop for Cointainer<'a, T> {
        fn drop(&mut self) {
            println!("dropping 1: {:p}", self.item)
        }
    }
    
    struct Cointainer<'a, T: 'a + ?Sized> {
        item: &'a mut T,
    }
    
    impl<'a, T> Cointainer<'a, T> {
        fn into_inner(self) -> &'a mut T {
            // I copied this code from Stack Overflow but didn't read
            // the warning about explaining why it's safe. Sure hope
            // this doesn't cause any bugs!
            unsafe {
                let x = self.item as *mut _;
                std::mem::forget(self);
                &mut *x
            }
        }
    
        fn mv(self) -> Cointainer2<'a, T> {
            let item = self.into_inner();
            Cointainer2 { item }
        }
    }
    
    struct Cointainer2<'a, T: 'a + ?Sized> {
        item: &'a mut T,
    }
    
    impl<'a, T: 'a + ?Sized> Drop for Cointainer2<'a, T> {
        fn drop(&mut self) {
            println!("dropping 2: {:p}", self.item)
        }
    }
    
    fn main() {
        let mut a = String::new();
        let c1 = Cointainer { item: &mut a };
        let c2 = c1.mv();
    }