Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One mutable borrow and multiple immutable borrows

I'm trying to write a program that spawns a background thread that continuously inserts data into some collection. At the same time, I want to keep getting input from stdin and check if that input is in the collection the thread is operating on.

Here is a boiled down example:

use std::collections::HashSet;
use std::thread;

fn main() {
    let mut set: HashSet<String> = HashSet::new();

    thread::spawn(move || {
        loop {
            set.insert("foo".to_string());
        }
    });

    loop {
        let input: String = get_input_from_stdin();

        if set.contains(&input) {
            // Do something...
        }
    }
}

fn get_input_from_stdin() -> String {
    String::new()
}

However this doesn't work because of ownership stuff.

I'm still new to Rust but this seems like something that should be possible. I just can't find the right combination of Arcs, Rcs, Mutexes, etc. to wrap my data in.

like image 784
David Pedersen Avatar asked Jan 30 '23 07:01

David Pedersen


1 Answers

First of all, please read Need holistic explanation about Rust's cell and reference counted types.


There are two problems to solve here:

  1. Sharing ownership between threads,
  2. Mutable aliasing.

To share ownership, the simplest solution is Arc. It requires its argument to be Sync (accessible safely from multiple threads) which can be achieved for any Send type by wrapping it inside a Mutex or RwLock.

To safely get aliasing in the presence of mutability, both Mutex and RwLock will work. If you had multiple readers, RwLock might have an extra performance edge. Since you have a single reader there's no point: let's use the simple Mutex.

And therefore, your type is: Arc<Mutex<HashSet<String>>>.

The next trick is passing the value to the closure to run in another thread. The value is moved, and therefore you need to first make a clone of the Arc and then pass the clone, otherwise you've moved your original and cannot access it any longer.

Finally, accessing the data requires going through the borrows and locks...

use std::sync::{Arc, Mutex};

fn main() {
    let set = Arc::new(Mutex::new(HashSet::new()));

    let clone = set.clone();
    thread::spawn(move || {
        loop {
            clone.lock().unwrap().insert("foo".to_string());
        }
    });

    loop {
        let input: String = get_input_from_stdin();

        if set.lock().unwrap().contains(&input) {
            // Do something...
        }
    }
}

The call to unwrap is there because Mutex::lock returns a Result; it may be impossible to lock the Mutex if it is poisoned, which means a panic occurred while it was locked and therefore its content is possibly garbage.

like image 175
Matthieu M. Avatar answered Feb 02 '23 10:02

Matthieu M.