azg*_*ult 5 ownership rust borrow-checker
我想在可变借入中替换一个值; 将其中的一部分移动到新值中:
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),
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码不起作用,并且可以理解的是,将值移出会self破坏它的完整性.但由于之后立即删除了该值,我(如果不是编译器)可以保证它的安全性.
有没有办法实现这个目标?我觉得这是一个不安全代码的工作,但我不确定这是如何工作的.
She*_*ter 11
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
此代码的更高级版本将防止此代码中出现恐慌,从而导致程序中止。
也可以看看:
上面的代码不起作用,可以理解的是,将值移出 self 会破坏它的完整性。
这并不完全是这里发生的情况。例如,同样的事情self可以很好地工作:
impl<T> Foo<T> {
fn switch(self) {
self = match self {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
}
}
}
Run Code Online (Sandbox Code Playgroud)
Rust 对于部分和全部移动来说绝对没问题。这里的问题是你并不拥有你想要移动的值——你只有一个可变的借用引用。您不能移出任何引用,包括可变引用。
事实上,这是经常请求的功能之一 - 一种特殊的参考,可以允许摆脱它。它将允许多种有用的模式。您可以在这里和这里找到更多信息。
同时,在某些情况下您可以使用std::mem::replace和std::mem::swap。这些函数允许您从可变引用中“获取”值,前提是您提供一些东西作为交换。
好吧,我想通了如何用一点点做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() {}
Run Code Online (Sandbox Code Playgroud)