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.