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

Fra*_*lik 8 rust

TLDR:我想用我从旧的构建的新的替换T后面&mut TTT

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

示例代码(游乐场):

struct T { s: String }

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

这显然会失败,因为 add impl 是按值String获取的self,因此需要能够移出T,但这是不可能的,因为T位于引用后面。

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

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

t.s = old_t + " world";
Run Code Online (Sandbox Code Playgroud)

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

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

map_in_place(t, |old_t: T| T { s: old_t.s + " world" });
Run Code Online (Sandbox Code Playgroud)

是否存在无法实现或通常无法实现的原因?

use*_*342 6

map_in_place[ ] 是否有不可能或不常见的原因?

Amap_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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但不幸的是,它并不健全。如果f()出现恐慌,*place将会被丢弃两次。首先,它会在展开 的范围时被删除f(),它认为它拥有收到的值。然后,它会被借用值的所有者第二次删除place,而借用值的所有者永远不会意识到它认为自己拥有的值实际上是垃圾,因为它已经被删除了。这甚至可以在操场上重现,其中简单的panic!()闭包会导致双重释放。

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

replace_with板条箱确实提供了这样的功能,并在出现恐慌时提供了多种恢复选项。从文档来看,作者敏锐地意识到了恐慌问题,因此如果您确实需要该功能,那么这可能是获取它的好地方。