Reputation: 1540
I'm trying to get my head around Rust. I've got an alpha version of 1.
Here's the problem I'm trying to program: I have a vector of floats. I want to set up some threads asynchronously. Each thread should wait for the number of seconds specified by each element of the vector, and return the value of the element, plus 10. The results need to be in input order.
It's an artificial example, to be sure, but I wanted to see if I could implement something simple before moving onto more complex code. Here is my code so far:
use std::thread;
use std::old_io::timer;
use std::time::duration::Duration;
fn main() {
let mut vin = vec![1.4f64, 1.2f64, 1.5f64];
let mut guards: Vec<thread::scoped> = Vec::with_capacity(3);
let mut answers: Vec<f64> = Vec::with_capacity(3);
for i in 0..3 {
guards[i] = thread::scoped( move || {
let ms = (1000.0f64 * vin[i]) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", vin[i]);
answers[i] = 10.0f64 + (vin[i] as f64);
})};
for i in 0..3 {guards[i].join(); };
for i in 0..3 {println!("{}", vin[i]); }
}
So the input vector is [1.4, 1.2, 1.5]
, and I'm expecting the output vector to be [11.4, 11.2, 11.5]
.
There appear to be a number of problems with my code, but the first one is that I get a compilation error:
threads.rs:7:25: 7:39 error: use of undeclared type name `thread::scoped`
threads.rs:7 let mut guards: Vec<thread::scoped> = Vec::with_capacity(3);
^~~~~~~~~~~~~~
error: aborting due to previous error
There also seem to be a number of other problems, including using vin
within a closure. Also, I have no idea what move
does, other than the fact that every example I've seen seems to use it.
Upvotes: 0
Views: 1700
Reputation: 31283
Your error is due to the fact that thread::scoped
is a function, not a type. What you want is a Vec<T>
where T
is the result type of the function. Rust has a neat feature that helps you here: It automatically detects the correct type of your variables in many situations.
If you use
let mut guards = Vec::with_capacity(3);
the type of guards
will be chosen when you use .push()
the first time.
There also seem to be a number of other problems.
you are accessing guards[i]
in the first for loop, but the length of the guards
vector is 0
. Its capacity is 3
, which means that you won't have any unnecessary allocations as long as the vector never contains more than 3 elements. use guards.push(x)
instead of guards[i] = x
.
thread::scoped
expects a Fn() -> T
, so your closure can return an object. You get that object when you call .join()
, so you don't need an answer-vector.
vin
is moved to the closure. Therefore in the second iteration of the loop that creates your guards, vin
isn't available anymore to be moved to the "second" closure. Every loop iteration creates a new closure.
i
is moved to the closure. I have no idea what's going on there. But the solution is to let inval = vin[i];
outside the closure, and then use inval
inside the closure. This also solves Point 3.
vin
is mutable. Yet you never mutate it. Don't bind variables mutably if you don't need to.
vin
is an array of f64
. Therefore (vin[i] as f64)
does nothing. Therefore you can simply use vin[i]
directly.
join
moves out of the guard. Since you cannot move out of an array, your cannot index into an array of guards and join the element at the specified index. What you can do is loop over the elements of the array and join each guard.
Basically this means: don't iterate over indices (for i in 1..3
), but iterate over elements (for element in vector
) whenever possible.
All of the above implemented:
use std::thread;
use std::old_io::timer;
use std::time::duration::Duration;
fn main() {
let vin = vec![1.4f64, 1.2f64, 1.5f64];
let mut guards = Vec::with_capacity(3);
for inval in vin {
guards.push(thread::scoped( move || {
let ms = (1000.0f64 * inval) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", inval);
10.0f64 + inval
}));
}
for guard in guards {
let answer = guard.join();
println!("{}", answer);
};
}
Upvotes: 5
Reputation: 2711
In supplement of Ker's answer: if you really need to mutate arrays within a thread, I suppose the most closest valid solution for your task will be something like this:
use std::thread::spawn;
use std::old_io::timer;
use std::sync::{Arc, Mutex};
use std::time::duration::Duration;
fn main() {
let vin = Arc::new(vec![1.4f64, 1.2f64, 1.5f64]);
let answers = Arc::new(Mutex::new(vec![0f64, 0f64, 0f64]));
let mut workers = Vec::new();
for i in 0..3 {
let worker_vin = vin.clone();
let worker_answers = answers.clone();
let worker = spawn( move || {
let ms = (1000.0f64 * worker_vin[i]) as i64;
let d = Duration::milliseconds(ms);
timer::sleep(d);
println!("Waited {}", worker_vin[i]);
let mut answers = worker_answers.lock().unwrap();
answers[i] = 10.0f64 + (worker_vin[i] as f64);
});
workers.push(worker);
}
for worker in workers { worker.join().unwrap(); }
for answer in answers.lock().unwrap().iter() {
println!("{}", answer);
}
}
In order to share vectors between several threads, I have to prove, that these vectors outlive all of my threads. I cannot use just Vec
, because it will be destroyed at the end of main
block, and another thread could live longer, possibly accessing freed memory. So I took Arc
reference counter, which guarantees, that my vectors will be destroyed only when the counter downs to zero.
Arc
allows me to share read-only data. In order to mutate answers
array, I should use some synchronize tools, like Mutex
. That is how Rust prevents me to make data races.
Upvotes: 1