diff --git a/src/doc/guide.md b/src/doc/guide.md index a2b3f22c155..b821f0de47b 100644 --- a/src/doc/guide.md +++ b/src/doc/guide.md @@ -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` and a +`Sender`. You can use the `.send()` method on the `Sender` end, and +receive the message on the `Receiver` side with the `recv()` method. This +method blocks until it gets a message. There's a similar method, `.try_recv()`, +which returns an `Option` 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