9

我正在尝试替换可变借用中的值;将其中的一部分移入新值:

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破坏它的完整性。但由于该值在之后立即被删除,我(如果不是编译器)可以保证它的安全性。

有没有办法做到这一点?我觉得这是不安全代码的工作,但我不确定它是如何工作的。

4

3 回答 3

5

上面的代码不起作用,可以理解的是,将值移出 self 会破坏它的完整性。

这并不是这里发生的事情。例如,同样的事情self会很好地工作:

impl<T> Foo<T> {
    fn switch(self) {
        self = match self {
            Foo::Bar(val) => Foo::Baz(val),
            Foo::Baz(val) => Foo::Bar(val),
        }
    }
}

Rust 对于部分和全部移动绝对没问题。这里的问题是你不拥有你试图移动的值——你只有一个可变的借用引用。您不能移出任何引用,包括可变引用。

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

同时,在某些情况下,您可以使用std::mem::replacestd::mem::swap。这些函数允许您从可变引用中“获取”一个值,前提是您提供一些东西作为交换。

于 2015-04-10T22:01:38.450 回答
4

好的,我想出了如何用一点unsafeness 和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() {}
于 2015-04-10T22:05:30.280 回答
4

mem:uninitialized自 Rust 1.39 起已被弃用,由MaybeUninit.

但是,这里不需要未初始化的数据。相反,您可以使用ptr::read来获取self.

此时,tmp枚举中的数据拥有所有权,但如果我们要 drop 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);
        }
    }
}

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

也可以看看:

于 2020-02-24T18:42:29.813 回答