暂时离开借来的内容

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)

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

也可以看看:


Vla*_*eev 7

上面的代码不起作用,可以理解的是,将值移出 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::replacestd::mem::swap。这些函数允许您从可变引用中“获取”值,前提是您提供一些东西作为交换。


azg*_*ult 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() {}
Run Code Online (Sandbox Code Playgroud)

  • 如果`T`有析构函数,这个程序将会失败.当你调用`swap`时,你用垃圾替换了`self`中的任何东西.然后你重新分配`*self`,Rust将插入一个析构函数的调用,它会试图破坏`*self`的"旧"值,现在它是垃圾.由于某种原因,[playpen](http://is.gd/uaIhEe)没有失败(但你可以看到那里的双重免费),但对我来说,当我编译并在本地运行它时程序核心转储. (4认同)
  • [`take`](https://docs.rs/take_mut/0.2.0/take_mut/fn.take.html) 展示了如何以通用方式执行此操作,并防止展开(改为中止)。 (2认同)