Cannot tell the compiler that my struct lives for the whole program

Hi guys,

Beginner here, so far I have been able to resolve problems with rust by myself but have been stuck on this for a day and at this point I need help.

I am trying to create a background thread that will run every X seconds. I have a struct that I need to use some fields from, e.g. at the very least the interval at which to run the thread. However, the compiler doesn't seem to know that my struct lives for the entirety of my program and I have no idea how to specify that. As far as I know you can specify lifetimes to references only, but of course, I cannot return a reference from a function. Thank you in advance.

Playground link: Rust Playground

Code:

use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;

struct A{
    interval: Duration
}

impl A {
    fn new() -> A {
        A { interval: Duration::from_secs(1) }
    }
    
    fn run_interval(&'static self) -> JoinHandle<()> {
        thread::spawn(move || loop {
            thread::sleep(self.interval);
            println!("doing stuff");
        })
    }
}

pub fn main() {
    let a = A::new();
    let th = a.run_interval();
    
    th.join().unwrap();
}

Error:

Compiling playground v0.0.1 (file:///playground)
error[E0597]: `a` does not live long enough
  --> src/main.rs:24:14
   |
24 |     let th = a.run_interval();
   |              ^ borrowed value does not live long enough
...
27 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

Before exploring the esoteric solutions, have you tried more straightforward approaches? The interval case is simple enough: move a copy of self.interval into the closure. In general you can likely share (copy or via Arc if need be) quite a bit of state between your struct and the thread.

1 Like

Yeah, you are absolutely right. I can share the interval with an Arc, in fact I have other fields in the same struct that I share with Arc, one reason of me getting at this point is it didn't come to me to use it for such a simple thing as an interval, which is absolutely my mistake.

On the other hand I think in some way solving the problem without Arc can be a beneficial learning experience, however that would be only if anyone wants to go there since apparently it has a much simpler solution.

@vitalyd thanks

If you really wanted to reify the 'static aspect, you could do any of:

  1. Use Box::leak to return a &'static A, eg from your new function. This, as the name implies, leaks that allocation.
  2. A as written in your example can be made into a static binding. But if allocations would be needed in a real case then ..
  3. Use the lazy_static crate to create a static A.
3 Likes

To be clear, Duration is a Copy type - you don’t need an Arc for it.

1 Like

I'm not sure what your needs are but in your example the simplest way is to make run_interval: fn run_interval(self) -> JoinHandle<()>

Another solution as @vitalyd said is to make a copy:

fn run_interval(&self) -> JoinHandle<()> {
    let interval = self.interval;
    thread::spawn(move || loop {
        thread::sleep(interval);
        println!("doing stuff");
    })
}
1 Like

@vitalyd Thank you for the examples. Really useful stuff!

@Letheed I actually needed the value afterward, so couldn't really move it. But the example is working great. Thank you!

TBH, unless you plan on mutating stuff in A, making a copy/clone of it is probably the simplest, unless the size of it is prohibitive.

That’s what I was getting at with pointing out that Duration is Copy (sorry, I’m on mobile in between subway stations so somewhat terse :slight_smile:). If you move a copy then you’re all good.

You’ll want to make sure to really understand what’s happening here as it’s a crucial building block/construct of Rust. Happy to try shedding more light a bit later if needed ...

Alright, it just never crossed my mind to make a copy of the whole struct but it makes perfect sense, especially since it's not big and is initialized only once (twice with the copy). I think I got it. I indeed agree this concept is crucial and wanted to get it right.

Thank you again.

From the other side, where the issue is not so much that the value needs to live the life of the program, but rather that rust can't tell how long the thread that uses it lives, the crossbeam crate has a mechanism for running threads that only live as long as the scope that launched them.

Perhaps not directly what you were looking for in this case, but useful to know about generally. Sometimes it's useful to re-frame the problem.

1 Like

@dcarosone Thank you for this suggestion. I definitely explored crossbeam as a possibility, also at some point I will move from threads to tokio. The thing is I didn't want to sort of work around the problem without knowing what it actually was.