2
votes

I can run this code

fn testf(host: &str) {}

fn start(host: &str) {
    testf(host);
    testf(host);
}

but for some reason, I can't run this one:

fn testf(host: &str) {}

fn start(host: &str) {
    thread::spawn(move || testf(host));
    thread::spawn(move || testf(host));
}

because of the following error

src/server.rs:30:5: 30:18 error: the type `[closure@src/server.rs:30:19: 30:38 host:&str]` does not fulfill the required lifetime
src/server.rs:30     thread::spawn(move || testf(host));
                     ^~~~~~~~~~~~~
note: type must outlive the static lifetime
error: aborting due to previous error

Can somebody explain me, what is wrong with it and how to fix it?

2

2 Answers

7
votes

Your closure captures a string slice, therefore its environment has lifetime no longer than that of this slice, but thread::spawn() requires its argument to have static lifetime:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
    where F: FnOnce() -> T, 
          F: Send + 'static, 
          T: Send + 'static

(note the F: 'static requirement)

This is necessary because when the thread spawned by thread::spawn() gets to run, the string from which the slice is taken may already be destroyed. Rust has actually prevented an error in your code!

There are several ways to fix it.

1) The simplest way would be to clone the string for each thread:

fn start(host: &str) {
    {
        let host = host.to_owned();
        thread::spawn(move || testf(&host));
    }
    {
        let host = host.to_owned();
        thread::spawn(move || testf(&host));
    }
}

This way each thread receives its own copy of the string which will be destroyed when the thread itself finishes.

2) If you know that your threads should finish before start() function ends, you can use a third-party librariy like crossbeam to pass references into spawned threads:

extern crate crossbeam;

fn start(host: &str) {
    crossbeam::scope(|scope| {
        scope.spawn(move || testf(host));
        scope.spawn(move || testf(host));
    });
}

This way start() will wait until both threads spawns in scoped() has finished before returning, making sure that whatever string host points to won't be destroyed prematurely.

Previously such functionality was included in the standard library, but the way it was implemented was found to be unsound, so it was deprecated; a proper replacement for this functionality is yet to be added back into the standard library.

3) Even another alternative would be to use Arc<String> to share the string between threads, but this would require more significant changes outside of start():

use std::sync::Arc;

fn start(host: Arc<String>) {
    {
        let host = host.clone();
        thread::spawn(move || testf(&host));
    }
    {
        let host = host.clone();
        thread::spawn(move || testf(&host));
    }
}

With this approach you need to keep your string inside an Arc (which is an "atomically reference counted" pointer), so this requires you to change the code which calls start(). Cloning is probably better. Of course, if you want to share not &str but &SomeStruct where SomeStruct is large and/or not cloneable, there is no way to avoid scoping xor Arc.

0
votes

Declaration of thread::spawn function tried to tell me about my problem :)

pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static

so, I can fix that using (host: &'static str) insted of (host: &str)

fn testf(host: &str) {}

fn start(host: &'static str) {
    thread::spawn(move || testf(host));
    thread::spawn(move || testf(host));
}

it works great for me