【问题标题】:Why can't I make a static reference to a value that never goes out of scope?为什么我不能对永远不会超出范围的值进行静态引用?
【发布时间】:2021-02-02 15:09:45
【问题描述】:

为什么我不能对一个永不超出范围的值进行 'static 引用?

如果它永远不会超出范围,例如函数调用永远不会在主线程中返回,那么只要范围拥有它,数据就会保持有效,这将在程序的生命周期内。在这种情况下,我也应该能够在程序的生命周期内引用它,因为它在这段时间内保持有效,对吧?这是我的问题的一个例子:

fn noreturn() -> ! {
    loop {}
}

fn main() {
    // This value is never dropped or moved, and so it should last
    // for 'static, right?
    let not_dropped = 0usize;
    // So I should be able to borrow it for 'static here, right?
    let permanent_ref: &'static usize = &not_dropped;
    // This never returns, and so the value is never dropped
    // and the data stays valid,
    noreturn() 
    // .. but the compiler complains that not_dropped is dropped
    // here even though it's never dropped. Why?
}

如果保证永远不会到达具有此值的范围的末尾,那么我应该能够永远持有对范围所拥有的有效值的引用,对吗?我在这里是否在概念上遗漏了什么?

【问题讨论】:

    标签: rust reference lifetime borrow-checker borrowing


    【解决方案1】:

    返回不等于不退出。例如,用panic 替换您的循环(签名保持不变)。然后添加一个丢弃时打印出来的值:

    fn noreturn() -> ! {
        panic!()
    }
    
    fn main() {
        let not_dropped = Noisy;
        noreturn()
    }
    
    struct Noisy;
    
    impl Drop for Noisy {
        fn drop(&mut self) {
            eprintln!("I was dropped");
        }
    }
    

    您会看到该值确实被删除了。

    另一种看待这个问题的方法是,如果您在子线程 1 中执行此操作,这会产生子线程 2。线程 2 引用了 &'static 值,然后线程 1 退出并且堆栈消失了。当线程 2 尝试访问引用的值时,它的内存不安全。

    如果noreturn 函数仅通过包含一个循环来保证不退出,就像我展示的示例中那样?这种情况应该是可能的吗?

    Rust 中没有表面语法来保证函数永远运行,因此编译器无法为您保证这一点。但是,如果您知道编译器不知道的内容,则可以使用unsafe 来表达。

    例如,也许您知道您的函数调用了process::abort(或者panic 通过编译器选项实现为中止),因此在函数退出后任何代码都无法运行。在这种情况下,我相信(但尚未验证)您可以使用 unsafe 将生命周期更改为 'static

    话虽如此,调用Box::leak 是获取&'static 值的更简单的方法。

    【讨论】:

    • "你会看到这个值确实被丢弃了。" 只有当 panic 没有设置为 abort 时。
    • @Acorn 确实如此。我将扩展现有的有关 abort 的文本以涵盖这一点。
    • @Masklinn 这是我回答的重点;你能建议我改进措辞的方法吗? -> ! 并不意味着“永不退出”,而是“永不返回”。例如。 panic 是一个发散函数(如答案所示),它导致线程退出但它永远不会返回控制权。我什至展示了在线程展开期间删除了堆栈变量,如果将其视为'static,则会导致内存不安全。
    • 啊,是的,你当然是对的。异议撤回。
    【解决方案2】:

    当一些事情在 Rust 中起初不太有意义时,答案几乎总是“你在多线程场景中考虑过这个吗?”

    not_dropped 存在于主线程的堆栈帧中,如果主线程崩溃或退出,那么您从主线程的堆栈帧传递给另一个线程的任何“静态”引用都将变得无效:

    use std::thread;
    
    fn no_return() -> ! {
        loop {}
    }
    
    fn main() {
        let not_dropped = 0; // note: can actually be dropped
        let permanent_ref: &'static i32 = &not_dropped; // note: not actually permanent
        
        // pass permanent_ref to 2nd thread
        thread::spawn(move || {
            // imagine this thread also does some infinite loop
            loop {
                println!("{}", permanent_ref);
            }
        });
    
        // now imagine this infinite loops crashes / panics
        no_return();
        // main thread exits, not_dropped is dropped
        // permanent_ref is now invalidated in 2nd thread
    }
    

    【讨论】:

    • 如果主线程崩溃或退出——那么整个程序就会终止。但是,仍然可以(如果不可取)从非主线程手动调用 main,因此该参数仍然有效。基本上,main 是特殊的,但不是那个特殊的。
    • Rust main 不是操作系统 main。 Rust main 可能退出,然后操作系统安排子线程在操作系统main 退出之前运行。另一个不安全的案例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-06
    • 2018-06-24
    • 2017-11-11
    相关资源
    最近更新 更多