1

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?

berkes
  • 26,996
  • 27
  • 115
  • 206
  • 1
    Use `Arc>` or `Arc>`. – Chayim Friedman Nov 10 '22 at 08:50
  • @ChayimFriedman: It sounds like this is indeed the missing key. I you feel like it, could you maybe copy that to an answer and elaborate short why Arc on it's own doesn't cut it, but a Mutex and RwLock does? – berkes Nov 10 '22 at 09:06

1 Answers1

1

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.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • When looking into Mutex and RwLock, I miss some background on why to choose which. Also, in the comment above, you propose `Arc>` or `Arc>`, when I would think the wrapping should be `Mutex>`. And I'm not sure if the Arc is needed at all, when wrapped like this. Is it? – berkes Nov 10 '22 at 09:25
  • 1
    @berkes I would choose `Mutex` unless there are many reads and few writes. Wrapping the `Arc` won't work as the `Arc` will still inhibit mutation. And you still need the `Arc` otherwise the value won't live long enough. – Chayim Friedman Nov 10 '22 at 09:27
  • I've tried to implement a version with RwLock and one with Mutex, but still get borrow errors. "use of moved value: `list`": I've put the simplified code on rust playground here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fca2111a3e8d8f6341a89d3f204dc91f . Any idea what I'm missing? – berkes Nov 10 '22 at 09:55
  • 1
    @berkes You're missing a clone to the `Arc`: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c37e4af5713acd4ffe57a2e1600f2aa0. – Chayim Friedman Nov 10 '22 at 09:59