How to use self while spawning a thread from method


#1

Hello,

I am new to Rust and I am trying to make the following code work. Basically, I need to spawn a thread from a type instance’s method and I need to use the self reference inside the thread closure. I am aware that the compiler is complaining because the lifetime of self is shorter than static lifetime, but I’m not sure how to deal with this scenario appropriately. Eventually, I need my thread to be able to access the fields and methods of self. Should I make self static? How? What if I had more than one instance of my type?

Any help would be greatly appreciated. Thanks.

Code:

use std::thread;


struct ThreadTest<'a> {

	string : &'a str,
	
}


impl<'a> ThreadTest<'a> {

	fn new(string: &'a str) -> ThreadTest<'a> {

		ThreadTest {string: string}
	}

	fn start (&'a mut self) {

		thread::spawn(move || {

			self.test();
		});
		
	}

	fn test(&self) {

		println!("{:?}", self.string);
	}


}


fn main() {

	let mut thread_test = ThreadTest::new("this is a string");
	thread_test.start();

	loop {};
}

#2

There are a few similar topics on this forum, which should be helpful:
https://users.rust-lang.org/search?q=spawn%20thread%20self

Short answer: if the thread is short-lived (finishes before the method’s end), use a scoped thread (eg. from the crossbeam crate). If the thread is long-lived, put the fields of your struct (or even the whole struct) in std::sync::Arc. Also, you will probably have to change &str to String.

PS. Put your code in a fence when posting, like that:

```rust
some_code
```

#3

Thank you for your answer. I’m not sure though how to put the whole struct in an Arc. I tried the following but it didn’t compile:


// in the implementation of the structure:

	fn start (&'a mut self) {

		let shared_self = Arc::new(self);

		let self_copy = shared_self.clone();

		thread::spawn(move || {

			let local_self = &self_copy;

			local_self.test();
		});
		
	}

Error is: the lifetime cannot outlive the lifetime 'a as defined on the block

Any working example on how to Arc the whole structure instance please?

Thanks


#4

To put the whole struct in Arc you’ll just need to actually two structs – the inner one and the outer one (although wrapping separate fields would be a better idea, especially if you don’t want the thread to acces the whole struct). You can implement start only on the outer one.

struct MyInner { s: String }
struct My { inner: Arc<MyInner> }

impl My {
	fn start(&self) {
		let local_self = self.inner.clone();
		thread::spawn(move || {
			local_self.test();
		});
	}
}

Playground link

Note that I’ve changed &str to String, which was a direct cause of the error (you can’t share references in non-scoped threads).

Note that if you want the thread to make changes to original fields, you’ll need to wrap this field in a Mutex or some atomic type.


#5

Thank you so much. It actually works and I am able to read the string field from my thread. However, I am struggling to implement the ability to change this string. As you can see, I wrapped the field in a Mutex but the compiler is telling me that I “cannot borrow immutable borrowed content as mutable” – although I did make sure that both self and local_self are mutable in my start() function:


use std::thread;
use std::sync::Arc;
use std::sync::Mutex;


struct MyInner { s: Mutex<String> }
struct My { inner: Arc<MyInner> }

impl My {

	fn new(s: String) -> My {

		My {inner: Arc::new(MyInner {s: Mutex::new(s)})}
	}

	fn start(&mut self) {

		let mut local_self = self.inner.clone();

		thread::spawn(move || {
			local_self.test();
		});
	}
}

impl MyInner {
	
	fn test(&mut self) {

		println!("{:?}", self.s);
		self.s = Mutex::new(String::from("Hello!"));
	}
}

fn main() {

	let mut thread_test = My::new(String::from("this is a string"));
	thread_test.start();

	loop {};
} 

Thank you for your help!


#6

OK I got it right now… In fact, I had to wrap the inner struct itself in a Mutex… Thanks again krdln. Here’s the final code to access the inner struct instance from the thread in read/write mode:


use std::thread;
use std::sync::Arc;
use std::sync::Mutex;


struct MyInner { s: String }
struct My { inner: Arc<Mutex<MyInner>> }

impl My {

	fn new(s: String) -> My {

		My {inner: Arc::new(Mutex::new(MyInner {s: s}))}
	}

	fn start(&mut self) {

		let local_self = self.inner.clone();

		thread::spawn(move || {

			local_self.lock().unwrap().test();
		});
	}
}

impl MyInner {
	
	fn test(&mut self) {

		println!("{:?}", self.s);
		self.s = String::from("and this is a modified string");
		println!("{:?}", self.s);
	}
}

fn main() {

	let mut thread_test = My::new(String::from("this is a string"));
	thread_test.start();

	loop {};
} 



#7

I’m happy you’ve made it work! You can also make the previous (with the String directly inside of Mutex) version work. You just need to change the signature of test to take &self, not &mut self and just use the existing mutex instead of creating a new one:

*self.s.lock().unwrap() = String::from("hello");

The reason you need &self, not &mut self, despite you’re actually mutating the struct, is because & is actually a shared reference, and &mut is an unique reference. For the “regular” types, like i32 or String shared also means immutable and unique means mutable.

In your case (still talking about the first solution), your test function definitely can’t have a unique access to MyInner, since it’s shared between threads. It’s the Mutex, that can temporarily give a unique(mut) access to its contents.

In your second solution, you’ve worked around this problem by putting the whole struct inside a Mutex, so test function can have unique access, as the struct is never shared directly (the Mutex is shared, the struct is “hidden” inside it). It’s definitely working, as you see, but it’s less elegant solution (imagine the struct had some other read-only fields, they would unnecessarily need .lock() to be read).

You should also try channels! They’re more efficient and less error-prone than mutexed fields. Maybe they fit your needs.

Also, I recommend post series about interior mutability, especially the second part, which explains sharing mutable memory between threads.


#8

Thanks a lot for the info. I’ll explore it…