Unable to pass Struct to threads

I can't figure out how to pass a struct with a vector to threads.

This is the best that I can come up with. Can you help me figure out the problem?

use std::thread;
use std::sync::{Mutex, Arc};

struct Dog<'a> {
    name: String,
    tag: &'a mut Vec<String>
    
}

fn main() {

    let mut c : Vec<String> = Vec::new();
    
    let b = Dog { name:"fluffy".to_string(), tag: &mut c };
    
    let f = Mutex::new( b);
    
    let safe = Arc::new(f);

    thread::spawn(move|| {
    
                // lock the value
                let mut a = safe.lock().unwrap();
                // mutate the value
               // *a.name = *"test";//String::from("ho");
               a.tag.push(String::from("hello"));
            });

}

THis gives this error

error[E0597]: `c` does not live long enough
  --> src/lib.rs:16:51
   |
16 |     let b = Dog { name:"fluffy".to_string(), tag: &mut c };
   |                                                   ^^^^^^ borrowed value does not live long enough
17 |     
18 |     let f = Mutex::new( b);
   |             -------------- argument requires that `c` is borrowed for `'static`
...
33 | }
   | - `c` dropped here while still borrowed

Even though this is really more of an edge-case when it's done in the main function as in your code, it's important to keep in mind that a thread that's spawned like this can/will continue running after the function you spawned it in, in this case the main function, returns. Upon returning, the local variables, including c, are dropped, but the spawned thread still holds a reference to it through the b: Dog<'_> struct, which can result in a use-after free.

This kind of situation is the reason why thread::spawn places a F: 'static bound on the closure you pass it; this bound has the effect that it rules out any data that contains short-lived references, such as your Dog struct. (Where short-lived really means "anything that lives (even marginally) shorter than the entire duration of the program".)

To lift this 'static bound, you could use API from the crossbeam crate, in particular the crossbeam::scope function, e.g. like this:

use std::sync::{Arc, Mutex};
use std::thread;

struct Dog<'a> {
    name: String,
    tag: &'a mut Vec<String>,
}

fn main() {
    let mut c: Vec<String> = Vec::new();

    let b = Dog {
        name: "fluffy".to_string(),
        tag: &mut c,
    };

    let f = Mutex::new(b);

    let safe = Arc::new(f);

    crossbeam::scope(|scope| {
        scope.spawn(|_| {
             // lock the value
            let mut a = safe.lock().unwrap();
            // mutate the value
            // *a.name = *"test";//String::from("ho");
            a.tag.push(String::from("hello"));
        });
        
        // do some more stuff while the spawned thread is running
        // ...
        // ...

    }).unwrap(); // will wait for the spawned thread at this point
}

(in the playground)


Of course, some natural alternative might be to ask the question whether Dog really needs to contain a &mut Vec<...> rather than an owned Vec. If you make it owned, then the problem goes away, too, since there is no (short-lived) reference anymore.

use std::thread;
use std::sync::{Mutex, Arc};

struct Dog {
    name: String,
    tag: Vec<String>
    
}

fn main() {

    let mut c : Vec<String> = Vec::new();
    
    let b = Dog { name:"fluffy".to_string(), tag: c };
    
    let f = Mutex::new( b);
    
    let safe = Arc::new(f);

    thread::spawn(move|| {
    
                // lock the value
                let mut a = safe.lock().unwrap();
                // mutate the value
               // *a.name = *"test";//String::from("ho");
               a.tag.push(String::from("hello"));
            });

}

(in the playground)


Also note that in either case the Arc and Mutex is not really needed (unless you also want to access the Dog from the main thread. With the crossbeam::scope approach, even when you want to share it between the two threads, you can skip the Arc and just use a Mutex.

Wonderful response.

Thank you!

Can I ask you a follow up question?

I have the same error when I move the threads into another function.

What can I do to make this work?

I have an Struct I created, I want to pass it to the threads for the threads to lock and modify.

Here is my example code

use std::thread;
use std::sync::{Mutex, Arc};

//#[derive(PartialOrd, Eq, Copy, Clone, Hash)]
struct Dog {
    name: String,
    tag: Vec<String>
    
}

fn run( d: &mut Dog){
    

    let f = Mutex::new( d);
    
    let safe = Arc::new(f);

    for _t in 0..3 {
    
        thread::spawn(move|| {
        
                    // lock the value
                    let mut a = safe.lock().unwrap();
                    // mutate the value
                   // *a.name = *"test";//String::from("ho");
                   a.tag.push(String::from("hello"));
        });
    }
}

fn main() {
    let c : Vec<String> = Vec::new();
    
    let b = Dog { name:"fluffy".to_string(), tag: c };
    
    run(&mut b);

}

Here is the same error

error[E0621]: explicit lifetime required in the type of `d`
  --> src/main.rs:20:9
   |
20 |         thread::spawn(move|| {
   |         ^^^^^^^^^^^^^ lifetime `'static` required

For more information about this error, try `rustc --explain E0621`.

Note that the error appearing again is because safe is an Arc<Mutex<&mut Dog>>, and the &mut reference in there is short-lived. The thing will actually work out with Arc<Mutex<...>> if you put the Dog into there by-value, and then give each thread its own copy of the Arc, e.g.

use std::sync::{Arc, Mutex};
use std::thread;

#[derive(Debug)]
struct Dog {
    name: String,
    tag: Vec<String>,
}

fn run(d: Dog) {
    let f = Mutex::new(d);

    let safe = Arc::new(f);

    let mut handles = vec![];
    for _t in 0..3 {
        let safe = safe.clone();
        handles.push(thread::spawn(move || {
            // lock the value
            let mut a = safe.lock().unwrap();
            // mutate the value
            // *a.name = *"test";//String::from("ho");
            a.tag.push(String::from("hello"));
        }));
    }
    
    // wait for all the threads
    for h in handles {
        h.join().unwrap();
    }
    
    dbg!(&safe); // debug output to check if it worked
}

fn main() {
    let c: Vec<String> = Vec::new();

    let b = Dog {
        name: "fluffy".to_string(),
        tag: c,
    };

    run(b);
}

Note that I've added some code to wait for all the threads to finish, and then print out what the safe value currently looks like.

If you want to observe the results in the main function, you'd need to create the Arc<Mutex<Dog>> there and pass a copy to run.


Or alternatively... if we use crossbeam::scope again, we can keep the &mut and skip the Arc, and also don't need to collect the handles ourself:

use std::sync::Mutex;

#[derive(Debug)]
struct Dog {
    name: String,
    tag: Vec<String>,
}

fn run(d: &mut Dog) {
    let safe = Mutex::new(d);

    crossbeam::scope(|s| {
        for _t in 0..3 {
            s.spawn(|_| {
                // lock the value
                let mut a = safe.lock().unwrap();
                // mutate the value
                // *a.name = *"test";//String::from("ho");
                a.tag.push(String::from("hello"));
            });
        }
    })
    .unwrap(); // waits for all the threads
}

fn main() {
    let c: Vec<String> = Vec::new();

    let mut b = Dog {
        name: "fluffy".to_string(),
        tag: c,
    };

    run(&mut b);

    dbg!(&b); // debug output to check if it worked
}

Thank you for your quick response.

So does that mean to pass reference to a thread I have to use crossbeam::scope?

There is no way to pass by reference using thread::spawn?

To do so in any way first you need to solve the problem @steffahn pointed above.

crossbeam::scope solve it by blocking the current function's execution until the spawned thread terminates before the scope is ended.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.