[rust-dev] closure types

Niko Matsakis niko at alum.mit.edu
Thu Jan 12 14:28:22 PST 2012


So, based on our recent discussions, I was planning to have `fn(T)->U` 
be the stack closure type (i.e., block) and `fn@` and `fn~` be the boxed 
and unique closure types respectively. `native fn` would be a bare 
function. But this design has never seemed quite right to me: for 
example, is there an explicit syntax for declaring a stack closure? If 
so, what is it? `fn() { ... }`?

Now I am thinking that it makes more sense for the type `fn(T)->U` to 
refer to "any kind of closure" (a kind of abstract supertype). Then 
there are three "concrete" closure types `fn@`, `fn~`, and `fn&`, each 
of which are subtypes of `fn(T) -> U`. Bare functions are still `native 
fn(T)->U`.

In some way this is more complex (4 types, not 3) but it also feels 
simpler and cleaner to me. Thoughts?

I have the changes needed to implement either scheme waiting to be 
pushed. I just didn't push them yet because I didn't feel 100% at ease 
with the original scheme. Also, below is my best effort to adapt the 
tutorial to this idea, which may or may not be a good explanation.


Niko

----


## Closures

Named functions, like those in the previous section, do not close over
their environment. Rust also includes support for closures, which are
functions that can access variables in the scope in which they are
created.

There are several forms of closures, each with its own role. The most
common type is called a 'stack closure' (written `fn&`), this is a
closure which has full access to its environment.

fn call_block_with_ten(b: fn&(int)) { b(10); }

let x = 20;
call_block_with_ten(fn&(arg: int) {
#info("x=%d, arg=%d", x, arg);
});

This defines a function that accepts a stack closure, and then calls
it with a simple closure that executes a log statement, accessing both
its argument and the variable `x` from its environment.

Because stack closures are actually allocated in the stack frame of
the function where they appear, they are very lightweight and
efficient. However, they can only be used in a restricted way so as
to ensure that they do not survive the scope in which they were
created. They are allowed to appear in function argument position and
in call position, but nowhere else.

Note: there is a [more compact, Ruby-like syntax](#shorthand) for
blocks which infers the types of the arguments. See below for details.

### Boxed closures

When you need to store a closure in a data structure, a block will not
do, since the compiler will refuse to let you store it. For this
purpose, Rust provides a type of closure that has an arbitrary
lifetime, written `fn@` (boxed closure, analogous to the `@` pointer
type described in the next section).

A boxed closure does not directly access its environment, but merely
copies out the values that it closes over into a private data
structure. This means that it can not assign to these variables, and
will not 'see' updates to them.

This code creates a closure that adds a given string to its argument,
returns it from a function, and then calls it:

use std;

fn mk_appender(suffix: str) -> fn@(str) -> str {
let f = fn@(s: str) -> str { s + suffix };
ret f;
}

fn main() {
let shout = mk_appender("!");
std::io::println(shout("hey ho, let's go"));
}

### Closure compatibility

It often happens that you want to write a function which can accept
any kind of closure (e.g., either a stack or a boxed closure). In
these cases, you can use the type `fn` without any sigil. Thus, when
writing a higher-order function that wants to do nothing with its
function argument beyond calling it, you should almost always specify
the type of that argument as `fn`, so that callers have the
flexibility to pass whatever they want.

fn call_twice(f: fn()) { f(); f(); }
call_twice(fn&() { "I am a block"; });
call_twice(fn@() { "I am a boxed closure"; });
fn bare_function() { "I am a plain function"; }
call_twice(bare_function);

`fn()` types are subject to the same restrictions as block types; that
is, they may only be passed as parameters or called.

### Unique closures

<a name="unique"></a>

Unique closures, written `fn~` in analogy to the `~` pointer type (see
next section), hold on to things that can safely be sent between
processes. They copy the values they close over, much like boxed
closures, but they also 'own' them—meaning no other code can access
them. Unique closures mostly exist for spawning new
[tasks](task.html).

### Shorthand syntax

<a name="shorthand"></a>

Rust provides a compact closure syntax which can be used to represent
any kind of closure. The syntax is similar to Ruby or Smalltalk,
where the arguments are placed in pipes (`|`). For example, `{|arg1,
arg| body}` defines a closure with two arguments. This syntax is
intended for use in function calls: the kind of of closure (stack,
boxed, unique) and the types of the arguments will be inferred based
on what the function expects to receive.

As a further simplification, if the final parameter to a function is a
closure, the closure need not be placed within parenthesis. You could,
for example, write...

let doubled = vec::map([1, 2, 3]) {|x| x*2};

`vec::map` is a function in the core library that applies its last
argument to every element of a vector, producing a new vector.

Even when a closure takes no parameters, you must still write the bars
for the parameter list, as in `{|| ...}`.


More information about the Rust-dev mailing list