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

7

TLDR:我想用由旧的T构造出来的新的T替换在&mut 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实现是按值获取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" });

这是不可能的或者常见做法吗?

1个回答

5

为什么 [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() 发生 panic,*place 将会被释放两次。首先,在解除 f() 作用域时,它认为自己拥有所接收的值并将其释放。然后,place 所借用的值的拥有者将再次释放它,因为它并不知道它自认为拥有的值实际上已经被释放了。即使在 playground 中简单地在闭包中使用 panic!(),也会导致双重释放,您可以点击这里查看

因此,实现 map_in_place 的方法本身也必须标记为 unsafe,并存在一个安全约定,即 f() 不会发生 panic。但由于 Rust 中几乎任何东西都可能会发生 panic(例如任何片段访问),很难确保这个安全约定,因此该函数可能还带有一些危险。

replace_with crate 提供了这样的功能,并在 panic 发生时提供了几个恢复选项。根据文档,作者非常清楚 panic 会发生的问题,因此如果您真的需要这个功能,那么从这里获取可能是一个不错的选择。


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