暂时离开借来的内容

时间:2015-04-10 21:12:56

标签: rust ownership 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),
        }
    }
}

上面的代码不起作用,并且理解如此,将值移出self会破坏它的完整性。但由于之后立即删除了该值,我(如果不是编译器)可以保证它的安全。

有没有办法实现这个目标?我觉得这是一个不安全代码的工作,但我不确定它是如何工作的。

3 个答案:

答案 0 :(得分:5)

好的,我想出了如何使用unsafestd::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的部分和全部动作绝对没问题。这里的问题是你没有拥有你想要移动的价值 - 你只有一个可变的借来的参考。你不能放弃任何参考,包括可变参考。

这实际上是经常要求的功能之一 - 一种特殊的参考,可以让它移出它。它将允许几种有用的模式。您可以找到更多herehere

与此同时,在某些情况下,您可以使用std::mem::replacestd::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);
        }
    }
}

此代码的更高级版本将防止从该代码冒泡而引起恐慌,而是导致程序中止。

另请参阅: