"borrowed value doesn't live long enough" at the end of main?

I'm new to rust - and it seems I don't get it.
Why does the following piece of code lead to an error?
As far as I understand it, "cap" and "body" are dropped both at the end - so why is he complaining?

use regex::Regex;
use std::thread;
fn main() {
    let body = String::from("Testing test text");
    let re = Regex::new(r###"(?ms)test"###).unwrap();
    let cap = re.captures(&body).unwrap();
    let ci = cap.get(1).unwrap().as_str();
    let mut hnds = vec![];
    for c in 0..4 {
        let hnd = thread::spawn(move || {
            println!("{}", ci);
        });
        hnds.push(hnd);
    }
    for hnd in hnds {
        hnd.join().unwrap();
    }
}

Greetz,
Nova

The closure argument to thread::spawn is required to be 'static, which means it may not capture any temporary (non-'static) references. That includes ci here, since it borrows data (body) that is not allocated for the whole lifetime of the program.

A typical way to avoid this constraint when you need to pass non-'static references to threads is to use the "scoped threads" provided by a crate like crossbeam or rayon. (Rust's standard library previously included scoped threads, but they were removed just before the 1.0 release when a soundness problem was discovered. crossbeam and rayon have fixed this soundness problem.)

1 Like

Super fast answer... thanks, I'll try that :slight_smile:

To elaborate, the reason that the closure passed to thread::spawn must be 'static is that a thread spawned in this way could keep running for an unbounded amount of time, including after the main thread exits [edit: reading the documentation again I don't think this is right]. So the compiler can't guarantee that a temporary reference passed to a spawned thread will remain valid for the whole time that thread runs. Scoped threads solve this by causing spawned threads to be automatically joined at the end of a defined scope:

A scope creates a clear boundary between variables outside the scope and threads inside the scope. Whenever a scope spawns a thread, it promises to join the thread before the scope ends. This way we guarantee to the borrow checker that scoped threads only live within the scope and can safely access variables outside it.

(from the crossbeam documentation)

Ok, that makes sense.
Just tried crossbeam and it works just perfect.
Also found the mpmc channels... exactly what I was looking for.
Thanks again :kissing_heart:

2 Likes

Why does the following piece of code lead to an error?

For example, the following events could happen:

  • thread_0's println! fails to write to stdout and panics (more realistically - something in a real workload panics)
  • thread_0's handle is joined, returns a result and the main thread panics as it unwraps the handle
  • the main thread unwinds and cleans up all the variables you've created
  • thread_1 wants to print and dereferences ci.

This is why references across threads must have a 'static lifetime (this really means "it must be valid for the lifetime of the entire program") and why the compiler refuses your program.

2 Likes

Another way it could go wrong is that you can actually call the main function like any other function. There's no guarantee that the program exits any time soon just because you returned from main.

3 Likes