Struct that has a member as reference in another thread

I'm trying to move struct, that has a member as a reference, to another thread and get the compile time error '... borrowed does not live long enough'.

use std::thread;

#[derive(Debug, Clone)]
struct Video {
    id: u32,
    name: String
}

struct VideoService<'a>  {
    list: &'a Vec<Video>
}

impl VideoService<'_> {
    fn new(videos: &Vec<Video>) -> VideoService {
        VideoService { list: videos }
    }
    
    fn get_by_id(&self, id: u32) -> Option<Video> {
        self.list.iter().filter(|video| {
            video.id == id
        }).collect::<Vec<_>>().get(0).cloned().cloned()
    }
}

pub fn run_video_service_v2() {
    let videos = vec![
        Video { id: 1, name: "AAA".to_string() },
        Video { id: 2, name: "BBB".to_string() }
    ];

    let video_service = VideoService::new(&videos);

    let handle = thread::spawn(move || {
        println!("{:?}", video_service.get_by_id(1));
    });
    handle.join();
}

--> src/video_service_v2.rs:30:43
|
30 | let video_service = VideoService::new(&videos);
| ------------------^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that videos is borrowed for 'static
...
36 | }
| - videos dropped here while still borrowed

I can fix it by moving videos variable into VideoService. But for learning rust I 'd like to send videos variable as reference.

I'd like to understand why I get this error. Is it because thread has lifetime beyond the function run_video_service_v2()?
If possible, please also give me the link to the The Rust Programming Language - The Rust Programming Language the section that explain about this so that I can get more understanding.

Yep.

To reason about this ask yourself what happens when the original function returns but the background thread keeps running?

The std::thread API makes no guarantee about when the function you spawn on a background thread will run, so you could imagine situations where the OS scheduler waits until your function returns and does some IO (doing IO typically blocks and will end the current thread's time slice) before giving the video_service.get_by_id() thread a chance to run.

If that happens, the video_service reference will point to a value that no longer exists and you'll have a bad time. The 'static bound on std::thread::spawn() is designed to prevent these sorts of scenarios.

1 Like

In structs, you should be using owning types capable of storing data. References are temporary and by definition do not store data.

Note that there's a very unfortunate confusion between Rust references (loans) and storing data "by reference". Vec stores data by reference (i.e. it contains a pointer to the heap, and the heap data isn't copied when the Vec is moved around), but a reference to a Vec (loan/view of a Vec) doesn't store anything.

So your error is about VideoService not having any Video objects stored in it, and getting torn away from the videos local variable that actually becomes the storage for it.

The correct, idiomatic approach is:

struct VideoService  {
    list: Vec<Video>
}
…
   fn new(videos: Vec<Video>) -> VideoService {
        VideoService { list: videos }
    }
2 Likes