rust 通过移动和Map原始值来替换可变引用后面的值

iugsix8n  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(100)

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

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

dbf7pr2w

dbf7pr2w1#

[ map_in_place ]不可行或通常无法完成的原因是什么?
一个map_in_place确实是可能的:

// XXX unsound, don't use
pub fn map_in_place<T>(place: &mut T, f: impl FnOnce(T) -> T) {
    let place = place as *mut T;
    unsafe {
        let val = std::ptr::read(place);
        let new_val = f(val);
        std::ptr::write(place, new_val);
    }
}

但不幸的是,它并不可靠。如果f()出现问题,*place将被丢弃两次。第一次,它将在展开f()的作用域时被丢弃,因为f()认为它拥有它所接收到的值。然后,它将被place所借用的值的所有者第二次丢弃。它从来没有注意到它认为它拥有的值实际上是垃圾,因为它已经被删除了。这甚至可以在in the playground中重现,其中闭包中的一个简单panic!()会导致双free。
由于这个原因,map_in_place的实现本身必须被标记为不安全的,并且有一个安全契约,f()不会发生恐慌。但是,由于Rust中几乎任何东西都可能发生恐慌(例如,任何切片访问),因此很难确保安全契约和函数会有点像一把猎枪。
replace_with crate确实提供了这样的功能,在出现恐慌的情况下有几个恢复选项。从文档来看,作者敏锐地意识到了恐慌问题,所以如果你真的需要那个功能,那可能是一个很好的地方。

相关问题