如何在具有可变字段的结构上实现移动语义



我有一个最小的代码示例,它为某些容器实现移动语义:

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() {}

编译和工作没有任何问题。

我意识到我需要改变Cointainer::item引用的值,所以我mut引用了。当我这样做时,我得到:

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

我需要创建一个新容器并在那里转移item的所有权,然后删除旧容器。

这个例子是人为的。实际的"移动"操作执行其他一些操作,并且不一定返回相同的容器类型。

引用规则规定:

  1. 在任何给定时间,您都可以拥有以下一项,但不能同时拥有:

    • 一个可变引用。
    • 任意数量的不可变引用。

具有不可变引用的代码可以工作,因为这些代码可以自由复制。具有可变引用的代码失败,因为据编译器所知,您需要有两个并发可变引用:new中保存的引用,然后mem::forget可能还需要它。

作为人类,我们认识到mem::forget不会进入我们结构的内脏。这就是unsafe代码的用途:当编译器无法保证我们拥有的代码真正安全时。

一个小的unsafe块和一些强制转换为原始指针和返回可以解决问题。与任何不安全的代码一样,它应该有一个大的注释块,解释为什么编译器不理解它以及为什么它是真正安全的。

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();
}

最新更新