将结构成员替换为使用先前值的新值

时间:2018-09-23 00:14:49

标签: syntax rust move-semantics unsafe

我有一个结构,该结构拥有某种特征类型的装箱值。该结构本身也实现了相同的特征。我想用相同结构的新实例替换该值,然后将其包装。

以下未编译的代码应使我更清楚地知道要做什么:

trait T {}

struct S {
    t: Box<dyn T>,
}
impl T for S {}

impl S {
    fn new(t: Box<dyn T>) -> Self {
        Self { t }
    }

    fn wrap_t(&mut self) {
        self.t = Box::new(Self::new(self.t))
    }
}

此操作失败:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:14:37
   |
14 |         self.t = Box::new(Self::new(self.t))
   |                                     ^^^^ cannot move out of borrowed content

像这样实现wrap_t会编译:

use std::mem;

fn wrap_t(&mut self) {
    unsafe {
        let old_t = mem::replace(&mut self.t, mem::uninitialized());
        let new_t = Box::new(Self::new(old_t));
        let uninit = mem::replace(&mut self.t, new_t);
        mem::forget(uninit);
    }
}

我想知道是否存在一种安全的方法。

1 个答案:

答案 0 :(得分:1)

您正在使用的唯一next函数是unsafe。您需要传递给mem::uninitialized的内容,但是实现mem::replace无效,因为Default返回default(),这会导致对象安全。同样,您无法实现Self来复制旧值,因为Clone还会返回clone()

不过,您可以为此目的实现一个虚拟类型:

Self

您现在也不需要在这里使用struct Dummy; impl T for Dummy {} fn wrap_t(&mut self) { let old_t = mem::replace(&mut self.t, Box::new(Dummy)); let new_t = Box::new(Self::new(old_t)); mem::replace(&mut self.t, new_t); } (我假设这样做是为了防止未初始化内存被删除时的未定义行为)。


作为mem::forget的替代方法,您可以自己滚动,克隆到Clone,避免方法签名中包含Box<dyn T>,因此特征保持对象安全:

Self
trait T: Debug {
    fn clone_in_box(&self) -> Box<dyn T>;
}

impl T for S {
    fn clone_in_box(&self) -> Box<dyn T> {
        Box::new(S {
            t: self.t.clone_in_box(),
        })
    }
}

还有另一种设计,当阅读代码时,它更容易理解。那只是消耗fn wrap_t(&mut self) { let cloned = self.clone_in_box(); let old_t = mem::replace(&mut self.t, cloned); let new_t = Box::new(Self::new(old_t)); mem::replace(&mut self.t, new_t); } 并返回一个新对象:

self

而不是这个:

fn wrap_t(self) -> Self {
    Self::new(Box::new(Self::new(self.t)))
}

您会这样做:

s.wrap_t();