Liam Germain
Liam Germain

Reputation: 23

Why can't I make a static reference to a value that never goes out of scope?

Why can't I make a 'static reference to a value that never goes out of scope?

If it never goes out of scope, like when a function call never returns in the main thread, then the data stays valid as long as the scope owns it, which would be for the life of the program. In that case, I should be able to reference it for the duration of the life of the program too since it stays valid for that duration, right? Here's an example of what my issue is:

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?
}

If the end of the scope with this value is guaranteed to never be reached, then I should be able to hold a reference to the valid values owned by the scope forever, right? Am I conceptually missing something here?

Upvotes: 2

Views: 321

Answers (2)

pretzelhammer
pretzelhammer

Reputation: 15105

When something doesn't quite make sense at first in Rust the answer is almost always "Have you considered this in a multi-threaded scenario?"

not_dropped lives in the stack frame of the main thread and if the main thread panics or exits then any "static" reference you passed from the main thread's stack frame to another thread would become invalid:

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
}

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 430554

Not returning is not equivalent to not exiting. For example, replace your loop with a panic (the signature stays the same). Then add a value that prints out when it is dropped:

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

fn main() {
    let not_dropped = Noisy;
    noreturn()
}

struct Noisy;

impl Drop for Noisy {
    fn drop(&mut self) {
        eprintln!("I was dropped");
    }
}

You'll see that the value is indeed dropped.

Another way of looking at this would be if you did this in child thread 1, which spawned child thread 2. Thread 2 has a reference to the &'static value, then thread 1 exits and the stack is gone. When thread 2 attempts to access the referred-to value, it's memory unsafety.

What if the noreturn function guaranteed no exit by just containing a loop, like in the example I showed? Is that a case where this should be possible?

There's no surface syntax in Rust to guarantee that a function runs forever, so the compiler cannot guarantee this for you. However, if you know something the compiler doesn't, you can use unsafe to express that.

For example, maybe you know that your function calls process::abort (or that panic is implemented as an abort via compiler options) so it's impossible that any code can run after the function exits. In that case, I believe (but have not verified) that you could use unsafe to change the lifetime to be 'static.

That being said, calling Box::leak is a much simpler way of getting a &'static value.

Upvotes: 3

Related Questions