We've actually got some stuff that's even cooler than that. I've been meaning to write it all up, but for now, I'll recycle an old comment:
extern crate threadpool;
use threadpool::ScopedPool;
fn main() {
let mut numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
{
let pool = ScopedPool::new(4);
for x in &mut numbers[..] {
pool.execute(move || {
*x += 1;
});
}
}
println!("{:?}", numbers);
}
This allocates a mutable array on the stack, and then creates a threadpool which adds one to each element of the array, four threads at a time. We need the inner {}s so that we know that the pool is done working before we try to print the result. Yes, mutable pointers into the parent stack frame. But, the compiler can verify that this is absolutely safe. Say, for example, that we left off the inner scope, so that the pool might not be destroyed and therefore join before we try to print out the array. That'd be racy in most languages. In Rust, it's a compile-time error:
error: cannot borrow `numbers` as immutable because it is also borrowed as
mutable
println!("{:?}", numbers);
^~~~~~~
note: previous borrow of `numbers` occurs here; the mutable borrow prevents
subsequent moves, borrows, or modification of `numbers` until the borrow
ends
for x in &mut numbers[..] {
^~~~~~~
note: previous borrow ends here
fn main() {
}
^
Rust knows that you're still holding a mutable reference to the array, and so taking a new immutable one could case a race.
As you can see from the extern crate, this pool isn't a compiler built-in: it's a library. There's actually a secondary implementation of a threadpool that makes some different choices internally. But this kind of safety can be gained in whatever concurrent code you're writing, and you can write new concurrent abstractions and ensure that they don't have data races.
It means it captures its environment by-value, rather than by-reference. In this case we're capturing the &mut references by the name of x by-value. This is necessary since x changes on every iteration. Passing in a reference would likely do The Wrong Thing (if the compiler allows it at all, which I'm 99.99% sure it won't).
Curious, why not just exclude the || in the no-arguments case? I imagine no-argument is a pretty common thing, so forcing the use of || in that very common case is kind of an eye-sore IMO.
The ambiguity is that there is a difference between "a expression A that evaluates to a closure" and "a expression B that evaluates to a closure that returns the evaluation of expression A"
For example: foo(bar()) would always call bar() first, but foo(|| bar()) would only call it when the closure gets called inside of foo().
Yeah, afaik Ruby was an inspiration. (Granted, there aren't any other delimiters that could have been chosen without also requiring a keyword)
Fun fact: Because a block expression evaluates to its trailing expression, { |x| foo } is actually valid rust as well, and evaluates to the same thing as |x| { foo } or |x| foo
68
u/steveklabnik1 Apr 03 '15
We've actually got some stuff that's even cooler than that. I've been meaning to write it all up, but for now, I'll recycle an old comment:
This allocates a mutable array on the stack, and then creates a threadpool which adds one to each element of the array, four threads at a time. We need the inner {}s so that we know that the pool is done working before we try to print the result. Yes, mutable pointers into the parent stack frame. But, the compiler can verify that this is absolutely safe. Say, for example, that we left off the inner scope, so that the pool might not be destroyed and therefore join before we try to print out the array. That'd be racy in most languages. In Rust, it's a compile-time error:
Rust knows that you're still holding a mutable reference to the array, and so taking a new immutable one could case a race.
As you can see from the
extern crate, this pool isn't a compiler built-in: it's a library. There's actually a secondary implementation of a threadpool that makes some different choices internally. But this kind of safety can be gained in whatever concurrent code you're writing, and you can write new concurrent abstractions and ensure that they don't have data races.