【问题标题】:Is there a way to force Rust to let me use a possibly moved value?有没有办法强制 Rust 让我使用可能移动的值?
【发布时间】:2018-01-07 21:26:32
【问题描述】:

我是否忘记了借用和移动的工作原理?

let mut v = vec![1, 2, 3]; // I have some uncopyable value

if false {
    let t = v; // I might do something that consumes it
}

println!("{:?}", v); // in some condition, I know for sure that I didn't consume it

我能以某种方式使用unsafe 子句告诉编译器信任我吗?

任何解决方案都必须没有运行时开销。

【问题讨论】:

  • 把它塞进一个选项?
  • @CodesInChaos 这仍然需要运行时开销。如果我用 C++ 编程,我就不必为了灵活性而牺牲性能。 Rust 声称它从不牺牲 c++ 的性能,我可以包装不安全的代码来处理这些场景。
  • 即使在 C++ 中这也很棘手,因为您需要确保在变量超出范围时析构函数不会运行。
  • 我现在意识到,在 c++ 中,当对向量进行移动时,它会经历与 rust 中将其设置为等于空向量相同的无效化。
  • @Evan 这是导致 Rust 可能比 C++ 的一件事。由于 Rust 没有必须 填充零值,因此它在概念上可以执行更少的操作。正如@CodesInChaos 所示,您可以通过将值替换为空的Vec 来选择该路径。

标签: rust


【解决方案1】:

没有。

我确定我没有吃过它

仅仅因为您没有编写任何不消耗它的代码并不意味着它没有被消耗。 Ownership and conditionally executed code 进一步讨论了基于类型和堆栈的丢弃标志的机制,但从概念上讲,您的代码是:

let v = vec![1, 2, 3];

if false {
    let _t = v;
    drop(_t);
} else {
    drop(v);
}

println!("{:?}", v);

一旦条件结束,您的价值就如同消失了一样。 (在实现方面,drops do happen at the end of the function,但语义没有表达出来)。

在某些情况下

该条件将是您的if 语句的else 块:

if false {
    let _t = v;
} else {
    println!("{:?}", v);
}

【讨论】:

  • 但是只有一次消费机会的位置可能并不总是像 if 那样简单(可能是一个循环)。一个更好的问题是,“你能证明总会有一些数学等价的 else 语句吗”。
  • @Evan 在循环中将值移出变量将无法编译,因为从第二次迭代开始该变量将无效。
【解决方案2】:

即使在不安全的代码中,编译器也不允许您访问可能已将值移出的变量。

一些解决方法:

  • 将其包装在Option 中。然后,您可以使用take 方法将数据移出,留下None 值。

    这是我为局部变量推荐的方法。

  • 用空向量替换原始向量。这很便宜,因为不会分配空向量。

    let t = std::mem::replace(&mut v, Vec::new());
    

    这是最接近C++移动的等价物,描述为:

    除非另有规定,否则此类移出的对象应 置于有效但未指定的状态。

  • 将其包裹在ManuallyDrop 中(这比mem::forget 更安全,因为它不会在发生恐慌时丢弃该值)。将它手动放在它仍然初始化的路径的末尾。使用 deref 在它仍然有效时访问它。 ptr::read 复制值,将原始位置视为无效/未初始化。

    这不应该有任何运行时开销,但我强烈建议不要在局部变量上使用它。不值得这么复杂和冒险。

    use std::mem::ManuallyDrop;
    use std::ptr;
    
    fn main() {
        let flag = //...;
        unsafe {
            let mut v = ManuallyDrop::new(vec![1, 2, 3]); // I have some uncopyable value
    
            if flag {
                let t = ptr::read(&*v); // I might do something that consumes it
                // don't touch *v from now on
                println!("{:?}", t);
            }
    
            if !flag {
                println!("{:?}", *v); // in some condition, I know for sure that I didn't consume it
                ManuallyDrop::drop(&mut v);
            }
        }
    }
    

    playground

【讨论】:

    【解决方案3】:

    如果你想模仿 C++ 移动语义,只需使用mem::replace

    use std::mem;
    
    let mut v = vec![1, 2, 3]; // I have some uncopyable value
    
    if false {
        let t = mem::replace(&mut v, vec![]); // I might do something that consumes it
    }
    
    println!("{:?}", v);
    

    vec![] 保证不分配内存,因此没有运行时开销。在一般情况下,就像在 C++ 中一样,您的不可复制类型需要一些“零”值。如果您无法得出“零”值,您可以随时使用 Option 作为 CodesInChaos 答案的建议。

    【讨论】:

    猜你喜欢
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-17
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多