Rust编译器如何知道值是否已被移动?

yyy*_*yyy 8 rust

一个简单的例子:

struct A;

fn main() {
    test(2);
    test(1);
}

fn test(i: i32) {
    println!("test");
    let a = A;
    if i == 2 {
        us(a);
    }
    println!("end");
}

impl Drop for A {
    fn drop(&mut self) {
        println!("drop");
    }
}

#[allow(unused_variables)]
fn us(a: A){
    println!("use");
}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,输出是:

test
use
drop
end
test
end
drop
Run Code Online (Sandbox Code Playgroud)

我理解在这种test(2)情况下,a被移动us(a),所以它的输出是"test-use-drop-end".

但是,在test(1)输出中,输出是"test-end-drop",这意味着编译器知道a没有移动.

如果us(a)被调用时,就不用砸atest(i),它会被丢弃在us(a); 如果us(a)没有被调用,a必须在之后删除println!("end").

由于编译器不可能知道是否us(a)被调用,编译器如何知道是否a.drop()应该调用println!("end")

Mat*_* M. 9

这在Rustnomicon中解释:

从Rust 1.0开始,drop flags实际上并没有秘密地隐藏在任何实现Drop的类型的隐藏字段中.

隐藏字段告诉当前值是否已被删除,如果没有,则表示当前值是否已被删除.因此,这在运行时是已知的,并且需要一些簿记.


展望未来,有一个RFC可以删除这些隐藏的字段.

RFC的想法是通过以下方式替换隐藏字段:

  1. 识别无条件丢弃(不需要任何运行时检查)
  2. 在函数框架中存储堆栈上的隐藏字段,以便有条件地删除这些值

这个新策略比旧策略有几个优点:

  • 主要的优点是,#[repr(C)]即使struct工具,现在总是会给出一个等同于C的表示Drop
  • 另一个重要的优点是节省内存(通过不膨胀struct大小)
  • 另一个小优点是由于无条件掉线和更好的缓存(减少内存大小)可能导致轻微的速度增加