1
Fork 0

Update guide/intro to take into account the removal of proc.

cc @steveklabnick
This commit is contained in:
Niko Matsakis 2014-11-26 10:02:46 -05:00
parent 27676d9aa9
commit 112faabf94
4 changed files with 79 additions and 117 deletions

View file

@ -30,7 +30,7 @@ fn print_message() { println!("I am running in a different task!"); }
spawn(print_message);
// Alternatively, use a `move ||` expression instead of a named function.
// `||` expressions evaluate to an unnamed closures. The `move` keyword
// `||` expressions evaluate to an unnamed closure. The `move` keyword
// indicates that the closure should take ownership of any variables it
// touches.
spawn(move || println!("I am also running in a different task!"));
@ -44,7 +44,7 @@ details to the standard library.
The `spawn` function has the type signature: `fn
spawn<F:FnOnce()+Send>(f: F)`. This indicates that it takes as
argument a closure (of type `F`) that it will run exactly once. This
closure is limited to capturing `Send`-able data form its environment
closure is limited to capturing `Send`-able data from its environment
(that is, data which is deeply owned). Limiting the closure to `Send`
ensures that `spawn` can safely move the entire closure and all its
associated state into an entirely different task for execution.

View file

@ -4235,36 +4235,16 @@ fn main() {
}
```
## Procs
## Moving closures
Rust has a second type of closure, called a **proc**. Procs are created
with the `proc` keyword:
```{rust}
let x = 5i;
let p = proc() { x * x };
println!("{}", p()); // prints 25
```
There is a big difference between procs and closures: procs may only be called once. This
will error when we try to compile:
```{rust,ignore}
let x = 5i;
let p = proc() { x * x };
println!("{}", p());
println!("{}", p()); // error: use of moved value `p`
```
This restriction is important. Procs are allowed to consume values that they
capture, and thus have to be restricted to being called once for soundness
reasons: any value consumed would be invalid on a second call.
Procs are most useful with Rust's concurrency features, and so we'll just leave
it at this for now. We'll talk about them more in the "Tasks" section of the
guide.
Rust has a second type of closure, called a **moving closure**. Moving
closures are indicated using the `move` keyword (e.g., `move || x *
x`). The difference between a moving closure and an ordinary closure
is that a moving closure always takes ownership of all variables that
it uses. Ordinary closures, in contrast, just create a reference into
the enclosing stack frame. Moving closures are most useful with Rust's
concurrency features, and so we'll just leave it at this for
now. We'll talk about them more in the "Tasks" section of the guide.
## Accepting closures as arguments
@ -5231,28 +5211,30 @@ concurrency libraries can be written for Rust to help in specific scenarios.
Here's an example of creating a task:
```{rust}
spawn(proc() {
spawn(move || {
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:
The `spawn` function takes a closure as an argument, and runs that
closure in a new task. Typically, you will want to use a moving
closure, so that the closure takes ownership of any variables that it
touches. This implies that those variables are not usable from the
parent task after the child task is spawned:
```{rust,ignore}
let mut x = vec![1i, 2i, 3i];
spawn(proc() {
spawn(move || {
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 borrow
checker catches the error.
`x` is now owned by the closure, 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 borrow checker 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
@ -5261,7 +5243,7 @@ work like this:
```{rust}
let (tx, rx) = channel();
spawn(proc() {
spawn(move || {
tx.send("Hello from a task!".to_string());
});
@ -5281,7 +5263,7 @@ If you want to send messages to the task as well, create two channels!
let (tx1, rx1) = channel();
let (tx2, rx2) = channel();
spawn(proc() {
spawn(move || {
tx1.send("Hello from a task!".to_string());
let message = rx2.recv();
println!("{}", message);
@ -5293,8 +5275,9 @@ 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.
The closure 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.
@ -5310,7 +5293,7 @@ a useful thing to use:
```{rust}
use std::sync::Future;
let mut delayed_value = Future::spawn(proc() {
let mut delayed_value = Future::spawn(move || {
// just return anything for examples' sake
12345i
@ -5318,18 +5301,18 @@ let mut delayed_value = Future::spawn(proc() {
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.
Calling `Future::spawn` works just like `spawn()`: it takes a
closure. In this case, though, you don't need to mess with the
channel: just have the closure 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.
The future 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
@ -5337,7 +5320,7 @@ Tasks don't always succeed, they can also panic. A task that wishes to panic
can call the `panic!` macro, passing a message:
```{rust}
spawn(proc() {
spawn(move || {
panic!("Nope.");
});
```
@ -5349,7 +5332,7 @@ notify other tasks that it has panicked. We can do this with `task::try`:
use std::task;
use std::rand;
let result = task::try(proc() {
let result = task::try(move || {
if rand::random() {
println!("OK");
} else {

View file

@ -391,26 +391,29 @@ Here's an example of a concurrent Rust program:
```{rust}
fn main() {
for _ in range(0u, 10u) {
spawn(proc() {
spawn(move || {
println!("Hello, world!");
});
}
}
```
This program creates ten threads, who all print `Hello, world!`. The `spawn`
function takes one argument, a `proc`. 'proc' is short for 'procedure,' and is
a form of closure. This closure is executed in a new thread, created by `spawn`
itself.
This program creates ten threads, who all print `Hello, world!`. The
`spawn` function takes one argument, a closure, indicated by the
double bars `||`. (The `move` keyword indicates that the closure takes
ownership of any data it uses; we'll have more on the significance of
this shortly.) This closure is executed in a new thread created by
`spawn`.
One common form of problem in concurrent programs is a 'data race.' This occurs
when two different threads attempt to access the same location in memory in a
non-synchronized way, where at least one of them is a write. If one thread is
attempting to read, and one thread is attempting to write, you cannot be sure
that your data will not be corrupted. Note the first half of that requirement:
two threads that attempt to access the same location in memory. Rust's
ownership model can track which pointers own which memory locations, which
solves this problem.
One common form of problem in concurrent programs is a 'data race.'
This occurs when two different threads attempt to access the same
location in memory in a non-synchronized way, where at least one of
them is a write. If one thread is attempting to read, and one thread
is attempting to write, you cannot be sure that your data will not be
corrupted. Note the first half of that requirement: two threads that
attempt to access the same location in memory. Rust's ownership model
can track which pointers own which memory locations, which solves this
problem.
Let's see an example. This Rust code will not compile:
@ -419,7 +422,7 @@ fn main() {
let mut numbers = vec![1i, 2i, 3i];
for i in range(0u, 3u) {
spawn(proc() {
spawn(move || {
for j in range(0, 3) { numbers[j] += 1 }
});
}
@ -432,8 +435,8 @@ It gives us this error:
6:71 error: capture of moved value: `numbers`
for j in range(0, 3) { numbers[j] += 1 }
^~~~~~~
7:50 note: `numbers` moved into closure environment here because it has type `proc():Send`, which is non-copyable (perhaps you meant to use clone()?)
spawn(proc() {
7:50 note: `numbers` moved into closure environment here
spawn(move || {
for j in range(0, 3) { numbers[j] += 1 }
});
6:79 error: cannot assign to immutable dereference (dereference is implicit, due to indexing)
@ -441,11 +444,16 @@ It gives us this error:
^~~~~~~~~~~~~~~
```
It mentions that "numbers moved into closure environment". Because we referred
to `numbers` inside of our `proc`, and we create three `proc`s, we would have
three references. Rust detects this and gives us the error: we claim that
`numbers` has ownership, but our code tries to make three owners. This may
cause a safety problem, so Rust disallows it.
It mentions that "numbers moved into closure environment". Because we
declared the closure as a moving closure, and it referred to
`numbers`, the closure will try to take ownership of the vector. But
the closure itself is created in a loop, and hence we will actually
create three closures, one for every iteration of the loop. This means
that all three of those closures would try to own `numbers`, which is
impossible -- `numbers` must have just one owner. Rust detects this
and gives us the error: we claim that `numbers` has ownership, but our
code tries to make three owners. This may cause a safety problem, so
Rust disallows it.
What to do here? Rust has two types that helps us: `Arc<T>` and `Mutex<T>`.
"Arc" stands for "atomically reference counted." In other words, an Arc will
@ -468,7 +476,7 @@ fn main() {
for i in range(0u, 3u) {
let number = numbers.clone();
spawn(proc() {
spawn(move || {
let mut array = number.lock();
(*array)[i] += 1;
@ -528,7 +536,7 @@ fn main() {
let vec = vec![1i, 2, 3];
for i in range(1u, 3) {
spawn(proc() {
spawn(move || {
println!("{}", vec[i]);
});
}

View file

@ -187,19 +187,18 @@ grammar as double-quoted strings. Other tokens have exact rules given.
<p id="keyword-table-marker"></p>
| | | | | |
|----------|----------|----------|----------|--------|
| abstract | alignof | as | be | box |
| break | const | continue | crate | do |
| else | enum | extern | false | final |
| fn | for | if | impl | in |
| let | loop | match | mod | move |
| mut | offsetof | once | override | priv |
| proc | pub | pure | ref | return |
| sizeof | static | self | struct | super |
| true | trait | type | typeof | unsafe |
| unsized | use | virtual | where | while |
| yield | | | | |
| | | | | |
|----------|----------|----------|----------|---------|
| abstract | alignof | as | be | box |
| break | const | continue | crate | do |
| else | enum | extern | false | final |
| fn | for | if | impl | in |
| let | loop | match | mod | move |
| mut | offsetof | once | override | priv |
| pub | pure | ref | return | sizeof |
| static | self | struct | super | true |
| trait | type | typeof | unsafe | unsized |
| use | virtual | where | while | yield |
Each of these keywords has special meaning in its grammar, and all of them are
@ -3842,8 +3841,6 @@ x = bo(5,7);
```{.ebnf .notation}
closure_type := [ 'unsafe' ] [ '<' lifetime-list '>' ] '|' arg-list '|'
[ ':' bound-list ] [ '->' type ]
procedure_type := 'proc' [ '<' lifetime-list '>' ] '(' arg-list ')'
[ ':' bound-list ] [ '->' type ]
lifetime-list := lifetime | lifetime ',' lifetime-list
arg-list := ident ':' type | ident ':' type ',' arg-list
bound-list := bound | bound '+' bound-list
@ -3852,8 +3849,6 @@ bound := path | lifetime
The type of a closure mapping an input of type `A` to an output of type `B` is
`|A| -> B`. A closure with no arguments or return values has type `||`.
Similarly, a procedure mapping `A` to `B` is `proc(A) -> B` and a no-argument
and no-return value closure has type `proc()`.
An example of creating and calling a closure:
@ -3876,30 +3871,6 @@ call_closure(closure_no_args, closure_args);
```
Unlike closures, procedures may only be invoked once, but own their
environment, and are allowed to move out of their environment. Procedures are
allocated on the heap (unlike closures). An example of creating and calling a
procedure:
```rust
let string = "Hello".to_string();
// Creates a new procedure, passing it to the `spawn` function.
spawn(proc() {
println!("{} world!", string);
});
// the variable `string` has been moved into the previous procedure, so it is
// no longer usable.
// Create an invoke a procedure. Note that the procedure is *moved* when
// invoked, so it cannot be invoked again.
let f = proc(n: int) { n + 22 };
println!("answer: {}", f(20));
```
### Object types
Every trait item (see [traits](#traits)) defines a type with the same name as