我要在可变借款中替换一个值;将其中的一部分移动到新值中:
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
*self = match self {
&mut Foo::Bar(val) => Foo::Baz(val),
&mut Foo::Baz(val) => Foo::Bar(val),
}
}
}
上面的代码不起作用,并且理解如此,将值移出self
会破坏它的完整性。但由于之后立即删除了该值,我(如果不是编译器)可以保证它的安全。
有没有办法实现这个目标?我觉得这是一个不安全代码的工作,但我不确定它是如何工作的。
答案 0 :(得分:5)
好的,我想出了如何使用unsafe
和std::mem
进行操作。
我用未初始化的临时值替换self
。由于我现在“拥有”过去的self
,我可以安全地移出它并替换它:
use std::mem;
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
// This is safe since we will overwrite it without ever reading it.
let tmp = mem::replace(self, unsafe { mem::uninitialized() });
// We absolutely must **never** panic while the uninitialized value is around!
let new = match tmp {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
};
let uninitialized = mem::replace(self, new);
mem::forget(uninitialized);
}
}
fn main() {}
答案 1 :(得分:4)
上面的代码不起作用,并且可以理解,移动值 出于自我而破坏了它的完整性。
这不完全是这里发生的事情。例如,与self
相同的东西可以很好地工作:
impl<T> Foo<T> {
fn switch(self) {
self = match self {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
}
}
}
Rust的部分和全部动作绝对没问题。这里的问题是你没有拥有你想要移动的价值 - 你只有一个可变的借来的参考。你不能放弃任何参考,包括可变参考。
这实际上是经常要求的功能之一 - 一种特殊的参考,可以让它移出它。它将允许几种有用的模式。您可以找到更多here和here。
与此同时,在某些情况下,您可以使用std::mem::replace
和std::mem::swap
。这些功能可让您“采取”#34;如果你交换了一些东西,那么这个值就是可变参考值。
答案 2 :(得分:2)
mem:uninitialized
从Rust 1.39开始被弃用,由MaybeUninit
取代。
但是,这里不需要未初始化的数据。相反,您可以使用ptr::read
来获取self
所引用的数据。
在这一点上,tmp
拥有枚举中数据的所有权,但是如果我们删除self
,该数据将试图被析构函数读取,从而导致内存不安全。
然后我们执行转换并将值放回去,以恢复类型的安全性。
use std::ptr;
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
// I copied this code from Stack Overflow without reading
// the surrounding text that explains why this is safe.
unsafe {
let tmp = ptr::read(self);
// Must not panic before we get to `ptr::write`
let new = match tmp {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
};
ptr::write(self, new);
}
}
}
此代码的更高级版本将防止从该代码冒泡而引起恐慌,而是导致程序中止。
另请参阅: