0

My code is like:

use std::{collections::HashSet, thread};

pub fn handle_server(blacklisted: Vec<String>) -> Vec<String> {
    return blacklisted;
}

pub fn handle_client() {
    return;
}

fn main() {
    let mut blacklisted = Vec::new();
    let ip_address_clone = ["100".to_string(), "200".to_string(), "300".to_string()];
    let self_ip = "100".to_string();

    for _ip in ip_address_clone.clone()
    //LEADER SENDS TO EVERY IP
    {
        if !blacklisted.clone().contains(&self_ip.clone()) {
            let mut blacklisted_clone = blacklisted.clone();

            let handle1 = thread::spawn(move || {
                let mut blacklisted_child = handle_server(blacklisted_clone.clone());

                blacklisted_clone.append(&mut blacklisted_child);

                let set: HashSet<_> = blacklisted_clone.drain(..).collect();

                blacklisted_clone.extend(set.into_iter());
            });
            blacklisted = blacklisted_clone;

            let handle2 = thread::spawn(move || {
                handle_client();
            });

            handle1.join().unwrap();
            handle2.join().unwrap();
        }
    }
}
error[E0382]: use of moved value: `blacklisted_clone`
  --> src/main.rs:31:27
   |
20 |             let mut blacklisted_clone = blacklisted.clone();
   |                 --------------------- move occurs because `blacklisted_clone` has type `Vec<String>`, which does not implement the `Copy` trait
21 |
22 |             let handle1 = thread::spawn(move || {
   |                                         ------- value moved into closure here
23 |                 let mut blacklisted_child = handle_server(blacklisted_clone.clone());
   |                                                           ----------------- variable moved due to use in closure
...
31 |             blacklisted = blacklisted_clone;
   |                           ^^^^^^^^^^^^^^^^^ value used here after move

As you can see, I am blacklisting some ip addresses based on return from handle_server().

I am updating blacklisted within the thread move, and also I am trying to access its value in the if statement in the line just below the loop. Therefore, I am trying to use a clone of blacklisted (to avoid using moved value). But in the line blacklisted = blacklisted_clone; I am updating the blacklisted with blacklisted_clone. Here I am getting use of moved value: blacklisted_clone error.

What should be my procedure?

Finomnis
  • 18,094
  • 1
  • 20
  • 27
Zubayr
  • 457
  • 4
  • 18
  • Does this answer your question? [How do I share a mutable object between threads using Arc?](https://stackoverflow.com/questions/31373255/how-do-i-share-a-mutable-object-between-threads-using-arc) – cafce25 Apr 22 '23 at 21:51
  • 1
    I think a combination of sharing a reference via [scoped threads](https://stackoverflow.com/questions/32750829/how-can-i-pass-a-reference-to-a-stack-variable-to-a-thread/32751956#32751956) and a [`Mutex`](https://stackoverflow.com/questions/31373255/how-do-i-share-a-mutable-object-between-threads-using-arc) will work well. – cafce25 Apr 22 '23 at 22:56

1 Answers1

2

There are a bunch of things wrong with your code. I'll structure this answer in two parts.

  • Fix the unrelated problems I can see, because without fixing them the fix for the moved value error would be very confusing
  • Solve the actual moved value error

Those are the things I will fix before talking about your actual question:

  • There is no reason to create blacklisted_clone. It simply makes the entire problem more confusing. Simply use the original blacklisted object.
  • It's very confusing (and slow) that you repeatedly move data back and forth between different datastructures. Don't use a Vec and a HashSet back and forth. Choose one and keep that. In your case, I would choose a HashSet, as your elements seem to be unique.
  • Don't pass an owned object to handle_server, as it will copy the blacklist yet again. Use a reference instead. Also, don't do .clone() whenever it suits you - try to use references. .clone() duplicates the entire data, so it is quite slow.

With those all fixed, the code would now look like this:

use std::{collections::HashSet, thread};

pub fn handle_server(ip: &str, _blacklisted: &HashSet<String>) -> Vec<String> {
    return vec![format!("{ip}_blacklisted")];
}

pub fn handle_client() {
    return;
}

fn main() {
    let mut blacklisted = HashSet::new();
    let ip_address_clone = ["100".to_string(), "200".to_string(), "300".to_string()];
    let self_ip = "100".to_string();

    for ip in ip_address_clone.clone()
    //LEADER SENDS TO EVERY IP
    {
        if !blacklisted.contains(&self_ip.clone()) {
            let handle1 = thread::spawn(move || {
                let blacklisted_child = handle_server(&ip, &blacklisted);
                // I assume `blacklisted_child` will contain new items that should get blacklisted,
                // and that need to be added to the original `blacklisted` object.
                blacklisted.extend(blacklisted_child);
            });

            let handle2 = thread::spawn(|| {
                handle_client();
            });

            handle1.join().unwrap();
            handle2.join().unwrap();
        }
    }
}
error[E0382]: borrow of moved value: `blacklisted`
  --> src\main.rs:19:13
   |
12 |     let mut blacklisted = HashSet::new();
   |         --------------- move occurs because `blacklisted` has type `HashSet<String>`, which does not implement the `Copy` trait
...
19 |         if !blacklisted.contains(&self_ip.clone()) {
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
20 |             let handle1 = thread::spawn(move || {
   |                                         ------- value moved into closure here, in previous iteration of loop

Now let's fix the original problem of value moved.

It appears because threads are not guaranteed to terminate at a given point; in this case, it is especially not guaranteed that main() outlives the thread. Therefore the thread cannot use anything that lives in main() (because main() could terminate while the thread still uses the data), and Rust forces you to make the closure a move || closure that moves the used items into the closure.

Sadly, moving values into a closure removes them from main(), which causes the errors you are seeing.

There are multiple ways to fix this, especially Arc and scoped threads.

In the past, mostly Arc was used and you will almost exclusively find Arc in previous answers. The reason is that scoped threads are fairly new. That said, they exist to fix your exact problem.

It basically means that you define a std::thread::scope around the thread, and Rust now understands how long the thread can maximally live. Threads spawned inside that scope will also automatically be joined at its end, so no explicit join is necessary.

And like that, it just simply works:

use std::{collections::HashSet, thread};

pub fn handle_server(ip: &str, blacklisted: &HashSet<String>) -> Vec<String> {
    println!("Handling {ip:?}. Already blacklisted: {blacklisted:?}");
    return vec![format!("{ip}_blacklisted")];
}

pub fn handle_client() {
    return;
}

fn main() {
    let mut blacklisted = HashSet::new();
    let ip_address_clone = ["100".to_string(), "200".to_string(), "300".to_string()];
    let self_ip = "100".to_string();

    for ip in ip_address_clone.clone()
    //LEADER SENDS TO EVERY IP
    {
        if !blacklisted.contains(&self_ip.clone()) {
            thread::scope(|s| {
                s.spawn(|| {
                    let blacklisted_child = handle_server(&ip, &blacklisted);
                    // I assume `blacklisted_child` will return new items that should get blacklisted,
                    // and that need to be added to the original `blacklisted` object.
                    blacklisted.extend(blacklisted_child);
                });

                s.spawn(|| {
                    handle_client();
                });
            })
        }
    }

    println!("{:?}", blacklisted);
}
Handling "100". Already blacklisted: {}
Handling "200". Already blacklisted: {"100_blacklisted"}
Handling "300". Already blacklisted: {"200_blacklisted", "100_blacklisted"}
{"200_blacklisted", "300_blacklisted", "100_blacklisted"}

Extra note:

In this case this only works because only one thread is using blacklisted simultaneously. If you would use it inside multiple threads, you would get an error that only one &mut reference can exist at a given time. In that case, you would need to use & references and interior mutability via Mutex. I won't go into detail here, though, as it isn't necessary in your case, but just wanted to mention it so you have heard of it already.

Finomnis
  • 18,094
  • 1
  • 20
  • 27