[rust-dev] closure types

Brian Anderson banderson at mozilla.com
Thu Jan 12 15:52:29 PST 2012


Maybe the tutorial section on functions should completely gloss over fn~, and save it for the section on tasks. It is just a tutorial, it doesn't have to justify every inch of the language.

----- Original Message -----
> From: "Brian Anderson" <banderson at mozilla.com>
> To: "Niko Matsakis" <niko at alum.mit.edu>
> Cc: rust-dev at mozilla.org
> Sent: Thursday, January 12, 2012 3:49:49 PM
> Subject: Re: [rust-dev] closure types
> 
> I like your new proposal but feel like the tutorial goes backwards.
> It should say 'pass a function around with type 'fn'; build
> functions with {|x| }; oh, but you might also be interested in all
> these caveats'.
> 
> ----- Original Message -----
> > From: "Niko Matsakis" <niko at alum.mit.edu>
> > To: rust-dev at mozilla.org
> > Sent: Thursday, January 12, 2012 2:28:22 PM
> > Subject: [rust-dev] closure types
> >
> > 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 `{|| ...}`.
> > _______________________________________________
> > Rust-dev mailing list
> > Rust-dev at mozilla.org
> > https://mail.mozilla.org/listinfo/rust-dev
> >
> 


More information about the Rust-dev mailing list