如何换出可变引用的值,暂时取得所有权?

How can I swap out the value of a mutable reference, temporarily taking ownership?

我有一个函数可以获取一些数据的所有权,对其进行破坏性修改,然后 returns 它。

fn transform(s: MyData) -> MyData {
    todo!()
}

在某些情况下,我有一个 &mut MyData 参考。我想将 transform 应用到 &mut MyData

fn transform_mut(data_ref: &mut MyData) {
    *data_ref = transform(*data_ref);
}

Rust Playground

但是,这会导致编译器错误。

error[E0507]: cannot move out of `*data_ref` which is behind a mutable reference
  --> src/lib.rs:10:27
   |
10 |     *data_ref = transform(*data_ref);
   |                           ^^^^^^^^^ move occurs because `*data_ref` has type `MyData`, which does not implement the `Copy` trait

我考虑过使用 mem::swapmem::replace,但它们要求您在取出另一个值之前已经有一些有效值可以放入引用中。

有什么办法可以做到这一点吗? MyData 没有合理的默认值或虚拟值可暂时存储在引用中。感觉好像因为我有独占访问权限所有者不应该关心转换,但我的直觉在这里可能是错误的。

您可以为 MyData 派生(或实现)Clone,然后将克隆传递到您的破坏性转换中。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e3a4d737a700ef6baa35160545f0e514

克隆消除了引用后面的数据问题。

It feels like because I have exclusive access the owner shouldn't care about the transformation, but my intuition might be wrong here.

这个想法的问题是,如果允许这样做,并且函数 transform 恐慌,*data_ref 中不再有有效值,如果 the unwind is caught or inside of Drop 可见处理内存 data_ref 指向。

例如,让我们以显而易见的方式实现这一点,只需将数据从引用对象中复制出来并返回:

use std::ptr;

fn naive_modify_in_place<T>(place: &mut T, f: fn(T) -> T) {
    let mut value = unsafe { ptr::read(place) };
    value = f(value);
    unsafe { ptr::write(place, value) };
}

fn main() {
    let mut x = Box::new(1234);
    naive_modify_in_place(&mut x, |x| panic!("oops"));
}

如果你运行这个程序(Rust Playground link),它会崩溃并出现“double free”错误。这是因为从 panicing 函数展开时丢弃了它的参数,然后从 main 展开时丢弃了 x — 这是同一个盒子,已经被释放。

有几个箱子是专门为解决这个问题而设计的: take_mut, and replace_with 打算对其进行改进。 (我都没有用过,特别是。)这两个都提供了两种处理恐慌的选择:

  1. 强制中止(程序立即退出,无法处理恐慌或清理其他任何东西)。
  2. 不同新计算的值替换引用的指称,因为恐慌开始时前一个值丢失了。

当然,如果您没有有效的替代值,那么您不能选择选项 2。在这种情况下,您可能 想考虑通过 添加占位符:如果你可以存储一个Option<MyData>并传递一个&mut Option<MyData>,那么你的代码可以使用Option::take暂时删除该值并保留None 代替它。 None 只会在出现恐慌时才可见,如果您的代码没有捕捉到恐慌,那么它永远不会重要。但这确实意味着每次访问数据都需要从 Option 中检索数据(例如使用 .as_ref().unwrap())。