auto merge of : steveklabnik/rust/guide_tasks, r=brson

I wasn't 100% sure of what level of detail I wanted to go into things here. For example, 1:1 vs M:N tasks seems like a better distinction to leave to the Guide.
This commit is contained in:
bors 2014-08-08 07:26:15 +00:00
commit c5b8d89b27

@ -4318,6 +4318,152 @@ the same function, so our binary is a little bit larger.
# Tasks
Concurrency and parallelism are topics that are of increasing interest to a
broad subsection of software developers. Modern computers are often multi-core,
to the point that even embedded devices like cell phones have more than one
processor. Rust's semantics lend themselves very nicely to solving a number of
issues that programmers have with concurrency. Many concurrency errors that are
runtime errors in other languages are compile-time errors in Rust.
Rust's concurrency primitive is called a **task**. Tasks are lightweight, and
do not share memory in an unsafe manner, preferring message passing to
communicate. It's worth noting that tasks are implemented as a library, and
not part of the language. This means that in the future, other concurrency
libraries can be written for Rust to help in specific scenarios. Here's an
example of creating a task:
```{rust}
spawn(proc() {
println!("Hello from a task!");
});
```
The `spawn` function takes a proc as an argument, and runs that proc in a new
task. A proc takes ownership of its entire environment, and so any variables
that you use inside the proc will not be usable afterward:
```{rust,ignore}
let mut x = vec![1i, 2i, 3i];
spawn(proc() {
println!("The value of x[0] is: {}", x[0]);
});
println!("The value of x[0] is: {}", x[0]); // error: use of moved value: `x`
```
`x` is now owned by the proc, and so we can't use it anymore. Many other
languages would let us do this, but it's not safe to do so. Rust's type system
catches the error.
If tasks were only able to capture these values, they wouldn't be very useful.
Luckily, tasks can communicate with each other through **channel**s. Channels
work like this:
```{rust}
let (tx, rx) = channel();
spawn(proc() {
tx.send("Hello from a task!".to_string());
});
let message = rx.recv();
println!("{}", message);
```
The `channel()` function returns two endpoints: a `Receiver<T>` and a
`Sender<T>`. You can use the `.send()` method on the `Sender<T>` end, and
receive the message on the `Receiver<T>` side with the `recv()` method. This
method blocks until it gets a message. There's a similar method, `.try_recv()`,
which returns an `Option<T>` and does not block.
If you want to send messages to the task as well, create two channels!
```{rust}
let (tx1, rx1) = channel();
let (tx2, rx2) = channel();
spawn(proc() {
tx1.send("Hello from a task!".to_string());
let message = rx2.recv();
println!("{}", message);
});
let message = rx1.recv();
println!("{}", message);
tx2.send("Goodbye from main!".to_string());
```
The proc has one sending end and one receiving end, and the main task has one
of each as well. Now they can talk back and forth in whatever way they wish.
Notice as well that because `Sender` and `Receiver` are generic, while you can
pass any kind of information through the channel, the ends are strongly typed.
If you try to pass a string, and then an integer, Rust will complain.
## Futures
With these basic primitives, many different concurrency patterns can be
developed. Rust includes some of these types in its standard library. For
example, if you wish to compute some value in the background, `Future` is
a useful thing to use:
```{rust}
use std::sync::Future;
let mut delayed_value = Future::spawn(proc() {
// just return anything for examples' sake
12345i
});
println!("value = {}", delayed_value.get());
```
Calling `Future::spawn` works just like `spawn()`: it takes a proc. In this
case, though, you don't need to mess with the channel: just have the proc
return the value.
`Future::spawn` will return a value which we can bind with `let`. It needs
to be mutable, because once the value is computed, it saves a copy of the
value, and if it were immutable, it couldn't update itself.
The proc will go on processing in the background, and when we need the final
value, we can call `get()` on it. This will block until the result is done,
but if it's finished computing in the background, we'll just get the value
immediately.
## Success and failure
Tasks don't always succeed, they can also fail. A task that wishes to fail
can call the `fail!` macro, passing a message:
```{rust}
spawn(proc() {
fail!("Nope.");
});
```
If a task fails, it is not possible for it to recover. However, it can
notify other tasks that it has failed. We can do this with `task::try`:
```{rust}
use std::task;
use std::rand;
let result = task::try(proc() {
if rand::random() {
println!("OK");
} else {
fail!("oops!");
}
});
```
This task will randomly fail or succeed. `task::try` returns a `Result`
type, so we can handle the response like any other computation that may
fail.
# Macros
# Unsafe