'static

If you tried to borrow a slice from the vector in the previous exercise, you probably got a compiler error that looks something like this:

error[E0597]: `v` does not live long enough
   |
11 | pub fn sum(v: Vec<i32>) -> i32 {
   |            - binding `v` declared here
...
15 |     let right = &v[split_point..];
   |                  ^ borrowed value does not live long enough
16 |     let left_handle = thread::spawn(move || left.iter().sum::<i32>());
   |                        ------------------------------------------------ 
                          argument requires that `v` is borrowed for `'static`
19 | }
   |  - `v` dropped here while still borrowed

argument requires that v is borrowed for 'static, what does that mean?

The 'static lifetime is a special lifetime in Rust.
It means that the value will be valid for the entire duration of the program.

Detached threads

A thread launched via thread::spawn can outlive the thread that spawned it.
For example:

#![allow(unused)]
fn main() {
use std::thread;

fn f() {
    thread::spawn(|| {
        thread::spawn(|| {
            loop {
                thread::sleep(std::time::Duration::from_secs(1));
                println!("Hello from the detached thread!");
            }
        });
    });
}
}

In this example, the first spawned thread will in turn spawn a child thread that prints a message every second.
The first thread will then finish and exit. When that happens, its child thread will continue running for as long as the overall process is running.
In Rust's lingo, we say that the child thread has outlived its parent.

'static lifetime

Since a spawned thread can:

  • outlive the thread that spawned it (its parent thread)
  • run until the program exits

it must not borrow any values that might be dropped before the program exits; violating this constraint would expose us to a use-after-free bug.
That's why std::thread::spawn's signature requires that the closure passed to it has the 'static lifetime:

#![allow(unused)]
fn main() {
pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static
{
    // [..]
}
}

'static is not (just) about references

All values in Rust have a lifetime, not just references.

In particular, a type that owns its data (like a Vec or a String) satisfies the 'static constraint: if you own it, you can keep working with it for as long as you want, even after the function that originally created it has returned.

You can thus interpret 'static as a way to say:

  • Give me an owned value
  • Give me a reference that's valid for the entire duration of the program

The first approach is how you solved the issue in the previous exercise: by allocating new vectors to hold the left and right parts of the original vector, which were then moved into the spawned threads.

'static references

Let's talk about the second case, references that are valid for the entire duration of the program.

Static data

The most common case is a reference to static data, such as string literals:

#![allow(unused)]
fn main() {
let s: &'static str = "Hello world!";
}

Since string literals are known at compile-time, Rust stores them inside your executable, in a region known as read-only data segment. All references pointing to that region will therefore be valid for as long as the program runs; they satisfy the 'static contract.

Further reading

Exercise

The exercise for this section is located in 07_threads/02_static