【问题标题】:replace a value behind a mutable reference by moving and mapping the original通过移动和映射原始值来替换可变引用后面的值
【发布时间】:2023-04-01 21:45:01
【问题描述】:

TLDR:我想将 &mut T 后面的 T 替换为从旧 T 构造的新 T

注意:如果这个问题的解决方案很容易找到,请原谅我。我做了很多谷歌搜索,但我不确定如何正确表达这个问题。

示例代码 (playground):

struct T { s: String }

fn main() {
    let ref mut t = T { s: "hello".to_string() };
    
    *t = T {
        s: t.s + " world"
    }
}

这显然失败了,因为 String 上的 add impl 采用 self 的值,因此需要能够移出 T,但这是不可能的,因为 T 在引用后面。

据我所知,实现这一目标的通常方法是做类似的事情

let old_t = std::mem::replace(t, T { s: Default::default() });

t.s = old_t + " world";

但这要求创建一些占位符 T 是可能且可行的,直到我们可以用真实数据填充它。

幸运的是,在我的用例中,我可以创建一个占位符 T,但我仍然不清楚为什么无法使用类似的 api:

map_in_place(t, |old_t: T| T { s: old_t.s + " world" });

是否有不可能或通常无法做到的原因?

【问题讨论】:

    标签: rust


    【解决方案1】:

    是否有原因 [map_in_place] 不可能或通常这样做?

    map_in_place 确实是可能的:

    // XXX unsound, don't use
    fn map_in_place<T>(t_ref: &mut T, f: impl FnOnce(T) -> T) {
        let t_ptr = t_ref as *mut T;
        drop(t_ref); // avoid UB due to dangling ref
        unsafe {
            let t = std::ptr::read(t_ptr);
            let new_t = f(t);
            std::ptr::write(t_ptr, new_t);
        }
    }
    

    但不幸的是,它并不健全。如果f() 发生恐慌,*t_ref 将被丢弃两次。首先它将在展开f() 的范围时被丢弃,它认为它拥有它收到的值。然后它将被借用值t_ref 的所有者第二次丢弃,它从未得到它认为它拥有的值实际上是垃圾的备忘录,因为它已经被丢弃了。这甚至可以复制in the playground,其中闭包中的简单panic!() 会导致双重释放。

    出于这个原因,map_in_place 的实现本身必须被标记为不安全,并带有一个f() 不会恐慌的安全合同。但是由于 Rust 中的几乎所有东西都可能出现恐慌(例如任何切片访问),因此很难确保安全契约和函数在某种程度上是枪。

    replace_with crate 确实提供了这样的功能,在出现紧急情况时有几个恢复选项。从文档来看,作者敏锐地意识到恐慌问题,所以如果你真的需要那个功能,那可能是一个很好的地方。

    【讨论】:

      猜你喜欢
      • 2017-06-03
      • 1970-01-01
      • 2013-01-12
      • 2019-12-04
      • 2021-03-05
      • 1970-01-01
      • 1970-01-01
      • 2022-10-02
      • 1970-01-01
      相关资源
      最近更新 更多