【问题标题】:Why RefCell's `into_inner` requires a move?为什么 RefCell 的 `into_inner` 需要移动?
【发布时间】:2020-06-15 06:33:24
【问题描述】:

我有一种情况,我必须通过&mut self 将结构从一个对象移动到另一个对象。看看:

pub struct MyStruct {
    //no copy trait
    device: Device
}

impl MyStruct {
    pub fn finalize(&mut self) {
        //error: cannot move since I borrowed 
        let interface = InterfaceBuilder::new(self.device)
    }
}

首先,为什么我不能从借来的可变引用中移出一些东西?借用的可变数据是独占的,不可能有其他代码在调查它。

好吧,为了解决这个问题,我改为

pub struct MyStruct {
    //no copy trait
    device: RefCell<Device>
}

impl MyStruct {
    pub fn finalize(&mut self) {
        //error on `self.device`: cannot move out of `self.device` which is behind a mutable reference
        let interface = InterfaceBuilder::new(self.device.into_inner())
    }
} 

我知道为什么会发生错误:

pub fn into_inner(self) -> T

调用into_inner 使self.device 移动。为什么RefCell 根本没有实现pub fn into_inner(&amp;mut self) -&gt; T?我看不出有什么问题。

【问题讨论】:

  • 在您的第一个版本中,不可能有其他代码正在调查它 现在,但原所有者可能会在稍后调查它,在finalize 返回并释放可变借用之后。
  • 如果into_inner没有消费,那么之后RefCell会是什么?
  • 你的 finalize 方法应该拥有 self: pub fn finalize (self) { … }
  • 我的猜测是您无法控制finalize() 的签名(例如,因为它是您无法控制的特征),但您可以控制MyStruct。如果这个猜测是正确的,那么一种解决方案是使用Option&lt;Device&gt;。您可以使用take() 方法移出&amp;mut Option&lt;_&gt;,从而将None 留在选项中。
  • 不管我是否猜对了您的问题,如果您能澄清问题以进一步解释您的要求,那就太好了。展示您解决问题的尝试很重要,但解释您尝试解决的实际问题对于获得有用的答案也很重要。

标签: rust


【解决方案1】:

您不能移出可变引用,因为这会使原始对象不完整。

考虑这段代码:

struct MyStruct {
    s: String
}

fn finalize(f: &mut MyStruct) {
    let _x = f.s; //error E0507!
}

fn main() {
    let mut my = MyStruct {
        s: "hi".into()
    };
    finalize(&mut my);
    println!("{}", my.s); //what should this do?
}

那么,RefCell::into_inner(&amp;mut self) -&gt; T 也有同样的问题。你可以连续调用它两次,你会得到两个 T 值,而之前只有一个。而且,对于非 Copy 类型是不可能的。

如果你想让这个函数消耗内部值,可能它也应该消耗外部值:

fn finalize(f: MyStruct) {
    let _x = f.s;
}

如果你真的想从可变引用中移出一个值,你必须在它的位置留下一些有效的东西。最简单的方法是声明一个Option 并使用take() 窃取并用None 替换它:

struct MyStruct {
    s: Option<String>
}

fn finalize(f: &mut MyStruct) {
    let _x = f.s.take();
}

当然,Option::take 返回一个Option,所以如果你调用它两次,第二次你会得到None。如果你是肯定的,你有一个价值,你可以做take().uwnrap()

或者,如果您的字段类型是 Default,您可以使用 std::mem::take 将其替换为默认创建的值:

struct MyStruct {
    s: Vec<i32>
}

fn finalize(f: &mut MyStruct) {
    let _x = std::mem::take(&mut f.s);
}

PS #1:有Cell::take(&amp;self) -&gt; T,但前提是T 实现Default。它的工作方式与 std::mem::take 类似,但引用不可变。

PS #2:还有unsafe fn ManuallyDrop::take(slot: &amp;mut ManuallyDrop&lt;T&gt;) -&gt; T,旨在用于高级drop 实现。但它是unsafe,所以它永远不应该是你的首选:如果你调用它两次,你会得到未定义的行为

【讨论】:

  • 如果T: DefaultOption&lt;T&gt; 不受欢迎,std::mem::replace 也是一个选项。
猜你喜欢
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 2015-07-18
  • 2017-04-20
  • 1970-01-01
  • 2015-01-15
  • 2010-12-03
  • 2020-04-26
相关资源
最近更新 更多