Update guide/intro to take into account the removal of proc
.
cc @steveklabnick
This commit is contained in:
parent
27676d9aa9
commit
112faabf94
4 changed files with 79 additions and 117 deletions
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue