Skip to content

Remove proc from the language #19338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 14, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/compiletest/compiletest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// except according to those terms.

#![crate_type = "bin"]
#![feature(phase, slicing_syntax, globs)]
#![feature(phase, slicing_syntax, globs, unboxed_closures)]

#![deny(warnings)]

Expand All @@ -23,6 +23,7 @@ use std::os;
use std::io;
use std::io::fs;
use std::str::FromStr;
use std::thunk::{Thunk};
use getopts::{optopt, optflag, reqopt};
use common::Config;
use common::{Pretty, DebugInfoGdb, DebugInfoLldb, Codegen};
Expand Down Expand Up @@ -369,16 +370,16 @@ pub fn make_test_closure(config: &Config, testfile: &Path) -> test::TestFn {
let config = (*config).clone();
// FIXME (#9639): This needs to handle non-utf8 paths
let testfile = testfile.as_str().unwrap().to_string();
test::DynTestFn(proc() {
test::DynTestFn(Thunk::new(move || {
runtest::run(config, testfile)
})
}))
}

pub fn make_metrics_test_closure(config: &Config, testfile: &Path) -> test::TestFn {
let config = (*config).clone();
// FIXME (#9639): This needs to handle non-utf8 paths
let testfile = testfile.as_str().unwrap().to_string();
test::DynMetricFn(proc(mm) {
test::DynMetricFn(box move |: mm: &mut test::MetricMap| {
runtest::run_metrics(config, testfile, mm)
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiletest/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) {
loop {
//waiting 1 second for gdbserver start
timer::sleep(Duration::milliseconds(1000));
let result = task::try(proc() {
let result = task::try(move || {
tcp::TcpStream::connect("127.0.0.1:5039").unwrap();
});
if result.is_err() {
Expand Down
50 changes: 28 additions & 22 deletions src/doc/guide-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,37 @@ with a closure argument. `spawn` executes the closure in the new task.
fn print_message() { println!("I am running in a different task!"); }
spawn(print_message);

// Alternatively, use a `proc` expression instead of a named function.
// The `proc` expression evaluates to an (unnamed) proc.
// That proc will call `println!(...)` when the spawned task runs.
spawn(proc() println!("I am also running in a different task!") );
// Alternatively, use a `move ||` expression instead of a named function.
// `||` 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!"));
```

In Rust, a task is not a concept that appears in the language semantics.
Instead, Rust's type system provides all the tools necessary to implement safe
concurrency: particularly, ownership. The language leaves the implementation
details to the standard library.

The `spawn` function has a very simple type signature: `fn spawn(f: proc():
Send)`. Because it accepts only procs, and procs contain only owned data,
`spawn` can safely move the entire proc and all its associated state into an
entirely different task for execution. Like any closure, the function passed to
`spawn` may capture an environment that it carries across tasks.
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 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.

```{rust}
# use std::task::spawn;
# fn generate_task_number() -> int { 0 }
// Generate some state locally
let child_task_number = generate_task_number();

spawn(proc() {
// Capture it in the remote task
spawn(move || {
// Capture it in the remote task. The `move` keyword indicates
// that this closure should move `child_task_number` into its
// environment, rather than capturing a reference into the
// enclosing stack frame.
println!("I am child number {}", child_task_number);
});
```
Expand All @@ -74,7 +80,7 @@ example of calculating two results concurrently:

let (tx, rx): (Sender<int>, Receiver<int>) = channel();

spawn(proc() {
spawn(move || {
let result = some_expensive_computation();
tx.send(result);
});
Expand Down Expand Up @@ -102,7 +108,7 @@ task.
# use std::task::spawn;
# fn some_expensive_computation() -> int { 42 }
# let (tx, rx) = channel();
spawn(proc() {
spawn(move || {
let result = some_expensive_computation();
tx.send(result);
});
Expand Down Expand Up @@ -135,13 +141,13 @@ results across a number of tasks? The following program is ill-typed:
# fn some_expensive_computation() -> int { 42 }
let (tx, rx) = channel();

spawn(proc() {
spawn(move || {
tx.send(some_expensive_computation());
});

// ERROR! The previous spawn statement already owns the sender,
// so the compiler will not allow it to be captured again
spawn(proc() {
spawn(move || {
tx.send(some_expensive_computation());
});
```
Expand All @@ -154,7 +160,7 @@ let (tx, rx) = channel();
for init_val in range(0u, 3) {
// Create a new channel handle to distribute to the child task
let child_tx = tx.clone();
spawn(proc() {
spawn(move || {
child_tx.send(some_expensive_computation(init_val));
});
}
Expand All @@ -179,7 +185,7 @@ reference, written with multiple streams, it might look like the example below.
// Create a vector of ports, one for each child task
let rxs = Vec::from_fn(3, |init_val| {
let (tx, rx) = channel();
spawn(proc() {
spawn(move || {
tx.send(some_expensive_computation(init_val));
});
rx
Expand Down Expand Up @@ -207,7 +213,7 @@ fn fib(n: u64) -> u64 {
12586269025
}

let mut delayed_fib = Future::spawn(proc() fib(50));
let mut delayed_fib = Future::spawn(move || fib(50));
make_a_sandwich();
println!("fib(50) = {}", delayed_fib.get())
# }
Expand Down Expand Up @@ -236,7 +242,7 @@ fn partial_sum(start: uint) -> f64 {
}

fn main() {
let mut futures = Vec::from_fn(200, |ind| Future::spawn( proc() { partial_sum(ind) }));
let mut futures = Vec::from_fn(200, |ind| Future::spawn(move || partial_sum(ind)));

let mut final_res = 0f64;
for ft in futures.iter_mut() {
Expand Down Expand Up @@ -278,7 +284,7 @@ fn main() {
for num in range(1u, 10) {
let task_numbers = numbers_arc.clone();

spawn(proc() {
spawn(move || {
println!("{}-norm = {}", num, pnorm(task_numbers.as_slice(), num));
});
}
Expand Down Expand Up @@ -312,7 +318,7 @@ if it were local.
# let numbers_arc = Arc::new(numbers);
# let num = 4;
let task_numbers = numbers_arc.clone();
spawn(proc() {
spawn(move || {
// Capture task_numbers and use it as if it was the underlying vector
println!("{}-norm = {}", num, pnorm(task_numbers.as_slice(), num));
});
Expand Down Expand Up @@ -344,7 +350,7 @@ result with an `int` field (representing a successful result) or an `Err` result
# use std::task;
# fn some_condition() -> bool { false }
# fn calculate_result() -> int { 0 }
let result: Result<int, Box<std::any::Any + Send>> = task::try(proc() {
let result: Result<int, Box<std::any::Any + Send>> = task::try(move || {
if some_condition() {
calculate_result()
} else {
Expand Down
85 changes: 34 additions & 51 deletions src/doc/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -5261,7 +5243,7 @@ work like this:
```{rust}
let (tx, rx) = channel();

spawn(proc() {
spawn(move || {
tx.send("Hello from a task!".to_string());
});

Expand All @@ -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);
Expand All @@ -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.
Expand All @@ -5310,34 +5293,34 @@ 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
});
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

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.");
});
```
Expand All @@ -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 {
Expand Down
Loading