Here's an example. You want to do some computations on an array of values:
fn main() {
let mut v = vec![1, 2, 3];
for i in &mut v {
*i += 1;
}
println!("v: {:?}", v);
}
They want to speed this up with threads. So they ask "how do I do threads in Rust" and get pointed to std::thread. So they write this code: use std::thread;
fn main() {
let mut v = vec![1, 2, 3];
for i in &mut v {
thread::spawn(move ||{
*i += 1;
});
}
println!("v: {:?}", v);
}
and they get this error message: error[E0597]: `v` does not live long enough
--> src/main.rs:6:18
|
6 | for i in &mut v {
| ^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `v` is borrowed for `'static`
...
13 | }
| - `v` dropped here while still borrowed
(there's more to the error message but I'm cutting it to the start)So they ask "hey how do I make v live for 'static" and someone says "you use Arc" so they write this:
use std::thread;
use std::sync::Arc;
fn main() {
let v = Arc::new(vec![1, 2, 3]);
for i in v.iter_mut() {
thread::spawn(move ||{
*i += 1;
});
}
println!("v: {:?}", v);
}
and get this error: error[E0596]: cannot borrow data in an `Arc` as mutable
--> src/main.rs:7:18
|
7 | for i in v.iter_mut() {
| ^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::sync::Arc<std::vec::Vec<i32>>`
So then they ask "hey I have an arc, but I want to mutate things inside of it, how do I do that?" and the answer is "use a mutex", so they write this: use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let v = Arc::new(Mutex::new(vec![1, 2, 3]));
for i in v.lock().unwrap().iter_mut() {
thread::spawn(move ||{
*i += 1;
});
}
println!("v: {:?}", v);
}
but this still doesn't work, because the lock is held during multiple threads of execution. So they figure out that they can do this: use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let v = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut joins = Vec::new();
for i in 0..3 {
let v = v.clone();
let handle = thread::spawn(move ||{
v.lock().unwrap()[i] += 1;
});
joins.push(handle);
}
for handle in joins {
handle.join().unwrap();
}
println!("v: {:?}", v);
}
I've skipped a few iterations here because this comment is already too large. The point is, they've now accomplished the task, but the boilerplate is way way way out of control.A more experienced Rust person would see this pattern and go "oh, hey, these threads don't actually live forever, because we want to join them all, but the compiler doesn't know that with thread::spawn because it's so general. What we want is scoped threads" and writes this:
use scoped_threadpool::Pool;
fn main() {
let mut pool = Pool::new(3);
let mut v = vec![1, 2, 3];
pool.scoped(|scope| {
for i in &mut v {
scope.execute(move ||{
*i += 1;
});
}
});
println!("v: {:?}", v);
}
and moves on with life. Way more efficient, way easier to write, extremely hard for a new person to realize that this is what they should be doing.