Reputation: 27553
I have a multithreaded program that wants to access resources across threads. Some want to write to them, some want to read from them.
I'm not sure if this counts as a global mutable singleton, because my setup isn't global, but the solution might be similar?
A very much simplified version would be the code below.
What it tries to do, is have one thread write to a struct, and another read from this same struct. So when thread A mutates the data, thread B would read the mutated data then.
use std::{thread, time::Duration};
struct TagList {
list: Vec<String>
}
impl TagList {
fn add(self: &mut TagList, tag: String) {
self.list.push(tag);
}
fn read(&self) -> Vec<String> {
self.list.clone()
}
}
fn main() {
let mut list = TagList { list: vec![] };
thread::spawn(move || {
["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
list.add(tag.to_string());
thread::sleep(Duration::from_millis(100));
});
});
thread::spawn(move || {
loop {
dbg!(list.read());
thread::sleep(Duration::from_millis(20));
}
});
}
This, rather obviously, fails with a borrow error:
error[E0382]: use of moved value: `list`
--> src/main.rs:79:19
|
70 | let mut list = TagList { list: vec![] };
| -------- move occurs because `list` has type `TagList`, which does not implement the `Copy` trait
71 |
72 | thread::spawn(move || {
| ------- value moved into closure here
73 | ["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
74 | list.add(tag.to_string());
| ---- variable moved due to use in closure
...
79 | thread::spawn(move || {
| ^^^^^^^ value used here after move
80 | dbg!(list.read());
| ---- use occurs due to use in closure
I've tried to solve this by wrapping the list in an Arc
:
use std::sync::Arc;
// ...
let list = Arc::new(TagList { list: vec![] });
let write_list = Arc::get_mut(&mut list).unwrap();
let read_list = Arc::clone(&list);
thread::spawn(move || {
["fee", "foo", "faa", "fuu"].into_iter().for_each(|tag| {
write_list.add(tag.to_string());
thread::sleep(Duration::from_millis(100));
});
});
thread::spawn(move || {
loop {
dbg!(read_list.read());
thread::sleep(Duration::from_millis(20));
}
});
This fails because I probably don't understand how Arc is supposed to work or how it relates to lifetimes:
error[E0597]: `list` does not live long enough
--> src/main.rs:71:35
|
71 | let write_list = Arc::get_mut(&mut list).unwrap();
| -------------^^^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `list` is borrowed for `'static`
...
85 | }
| - `list` dropped here while still borrowed
error[E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable
--> src/main.rs:72:32
|
71 | let write_list = Arc::get_mut(&mut list).unwrap();
| -----------------------
| | |
| | mutable borrow occurs here
| argument requires that `list` is borrowed for `'static`
72 | let read_list = Arc::clone(&list);
| ^^^^^ immutable borrow occurs here
Is what I want possible at all? (I'm pretty sure i've seen this used with e.g. std::sync::mpsc
where somehow messages are pushed and read over threads).
What should I use? Is Arc
the proper structure for this, or am I looking at the wrong solution there? What should I read up on to understand how such problems are typically solved in Rust?
Upvotes: 1
Views: 465
Reputation: 70910
Arc
does not allow mutation, and Arc::get_mut()
is not a solution to that. It allows mutation when the Arc
has only one instance (thus the second error) and return a reference which is not 'static
, therefore you cannot move it into a thread (the first error).
If you need to mutate the content of an Arc
, use Mutex
or RwLock
.
Upvotes: 1