David Castillo
David Castillo

Reputation: 4465

Cannot infer an appropriate lifetime when sharing self reference

This is an experiment I'm doing while learning Rust and following Programming Rust.

Here's a link to the code in the playground.

I have a struct (Thing) with some inner state (xs). A Thing should be created with Thing::new and then started, after which the user should choose to call some other function like get_xs.

But! In start 2 threads are spawned which call other methods on the Thing instance that could mutate its inner state (say, add elements to xs), so they need a reference to self (hence the Arc). However, this causes a lifetime conflict:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:18:30
   |
18 |         let self1 = Arc::new(self);
   |                              ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined 
on the method body at 17:5...
  --> src/main.rs:17:5
   |
17 | /     fn start(&self) -> io::Result<Vec<JoinHandle<()>>> {
18 | |         let self1 = Arc::new(self);
19 | |         let self2 = self1.clone();
20 | |
...  |
33 | |         Ok(vec![handle1, handle2])
34 | |     }
   | |_____^
note: ...so that expression is assignable (expected &Thing, found &Thing)
  --> src/main.rs:18:30
   |
18 |         let self1 = Arc::new(self);
   |                              ^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:23:20: 25:14 
self1:std::sync::Arc<&Thing>]` will meet its required lifetime bounds
  --> src/main.rs:23:14
   |
23 |             .spawn(move || loop {
   |              ^^^^^

Is there a way of spawning the state-mutating threads and still give back ownership of thing after running start to the code that's using it?

use std::io;
use std::sync::{Arc, LockResult, RwLock, RwLockReadGuard};
use std::thread::{Builder, JoinHandle};

struct Thing {
    xs: RwLock<Vec<String>>
}

impl Thing {

    fn new() -> Thing {
        Thing {
            xs: RwLock::new(Vec::new()),
        }
    }

    fn start(&self) -> io::Result<Vec<JoinHandle<()>>> {
        let self1 = Arc::new(self);
        let self2 = self1.clone();

        let handle1 = Builder::new()
            .name("thread1".to_owned())
            .spawn(move || loop {
                 self1.do_within_thread1();
            })?;

        let handle2 = Builder::new()
            .name("thread2".to_owned())
            .spawn(move || loop {
                self2.do_within_thread2();
            })?;

        Ok(vec![handle1, handle2])
    }

    fn get_xs(&self) -> LockResult<RwLockReadGuard<Vec<String>>> {
        return self.xs.read();
    }

    fn do_within_thread1(&self) {
        // read and potentially mutate self.xs
    }

    fn do_within_thread2(&self) {
        // read and potentially mutate self.xs
    }
}

fn main() {
    let thing = Thing::new();
    let handles = match thing.start() {
        Ok(hs) => hs,
        _ => panic!("Error"),
    };

    thing.get_xs();

    for handle in handles {
        handle.join();
    }
}

Upvotes: 1

Views: 603

Answers (1)

E_net4
E_net4

Reputation: 30042

The error message says that the value passed to the Arc must live the 'static lifetime. This is because spawning a thread, be it with std::thread::spawn or std::thread::Builder, requires the passed closure to live this lifetime, thus enabling the thread to "live freely" beyond the scope of the spawning thread.

Let us expand the prototype of the start method:

fn start<'a>(&'a self: &'a Thing) -> io::Result<Vec<JoinHandle<()>>> { ... }

The attempt of putting a &'a self into an Arc creates an Arc<&'a Thing>, which is still constrained to the lifetime 'a, and so cannot be moved to a closure that needs to live longer than that. Since we cannot move out &self either, the solution is not to use &self for this method. Instead, we can make start accept an Arc directly:

fn start(thing: Arc<Self>) -> io::Result<Vec<JoinHandle<()>>> {
    let self1 = thing.clone();
    let self2 = thing;

    let handle1 = Builder::new()
        .name("thread1".to_owned())
        .spawn(move || loop {
             self1.do_within_thread1();
        })?;

    let handle2 = Builder::new()
        .name("thread2".to_owned())
        .spawn(move || loop {
            self2.do_within_thread2();
        })?;

    Ok(vec![handle1, handle2])
}

And pass reference-counted pointers at the consumer's scope:

let thing = Arc::new(Thing::new());
let handles = Thing::start(thing.clone()).unwrap_or_else(|_| panic!("Error"));

thing.get_xs().unwrap();

for handle in handles {
    handle.join().unwrap();
}

Playground. At this point the program will compile and run (although the workers are in an infinite loop, so the playground will kill the process after the timeout).

Upvotes: 1

Related Questions