为什么我不能对一个永远不会超出范围的值进行静态引用?

问题描述

为什么我不能对一个永远不会超出范围的值进行 '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?
}

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

解决方法

返回不等于不退出。例如,用 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 值的更简单的方法。

,

当一开始在 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
}