使用前一个值替换结构体成员的新值

4
我有一个结构体拥有一个实现某些 trait 类型的装箱值。该结构体本身也实现了相同的 trait。我想用一个包装它的新结构体实例替换这个值。
以下代码不能通过编译,应该更清楚地说明了我的意图:
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
如果值在移回之前被移出,但代码发生恐慌,您认为应该发生什么行为? - Shepmaster
@Shepmaster 我没有一个好的答案。在我的真实程序中,我只调用那些保证不会出现 panic 的代码。 - Brennan Vincent
1个回答

1
你所使用的唯一一个不安全的函数是`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();

你会这样做:

s = s.wrap_t();

第一种解决方案可以工作,但是有点烦人,因为 T 可能有很多方法。我想对于 Dummy,我必须实现所有这些方法并进行存根实现。第二种解决方案对我不起作用,因为 wrap_t 可能会从 S 的另一个方法中调用,所以它确实需要通过引用获取 self - Brennan Vincent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接