Edit some things, make sure all code runs.
6.1 KiB
Functions
Functions (like all other static declarations, such as type
) can be
declared both at the top level and inside other functions (or modules,
which we'll come back to in moment).
The ret
keyword immediately returns from a function. It is
optionally followed by an expression to return. In functions that
return ()
, the returned expression can be left off. A function can
also return a value by having its top level block produce an
expression (by omitting the final semicolon).
Some functions (such as the C function exit
) never return normally.
In Rust, these are annotated with the pseudo-return type '!
':
fn dead_end() -> ! { fail; }
This helps the compiler avoid spurious error messages. For example,
the following code would be a type error if dead_end
would be
expected to return.
# fn can_go_left() -> bool { true }
# fn can_go_right() -> bool { true }
# enum dir { left; right; }
# fn dead_end() -> ! { fail; }
let dir = if can_go_left() { left }
else if can_go_right() { right }
else { dead_end(); };
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 'block', this is a closure which has full access to its environment.
fn call_block_with_ten(b: block(int)) { b(10); }
let x = 20;
call_block_with_ten({|arg|
#info("x=%d, arg=%d", x, arg);
});
This defines a function that accepts a block, and then calls it with a
simple block that executes a log statement, accessing both its
argument and the variable x
from its environment.
Blocks can only be used in a restricted way, because it is not allowed to survive the scope in which it was created. They are allowed to appear in function argument position and in call position, but nowhere else.
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
A nice property of Rust closures is that you can pass any kind of
closure (as long as the arguments and return types match) to functions
that expect a block
. 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 block
, so
that callers have the flexibility to pass whatever they want.
fn call_twice(f: block()) { f(); f(); }
call_twice({|| "I am a block"; });
call_twice(fn@() { "I am a boxed closure"; });
fn bare_function() { "I am a plain function"; }
call_twice(bare_function);
Unique closures
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 to for spawning new
tasks.
Shorthand syntax
The compact syntax used for blocks ({|arg1, arg2| body}
) can also
be used to express boxed and unique closures in situations where the
closure style can be unambiguously derived from the context. Most
notably, when calling a higher-order function you do not have to use
the long-hand syntax for the function you're passing, since the
compiler can look at the argument type to find out what the parameter
types are.
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 {|| ...}
.
Binding
Partial application is done using the bind
keyword in Rust.
let daynum = bind vec::position(_, ["mo", "tu", "we", "do",
"fr", "sa", "su"]);
Binding a function produces a boxed closure (fn@
type) in which some
of the arguments to the bound function have already been provided.
daynum
will be a function taking a single string argument, and
returning the day of the week that string corresponds to (if any).
Iteration
Functions taking blocks provide a good way to define non-trivial iteration constructs. For example, this one iterates over a vector of integers backwards:
fn for_rev(v: [int], act: block(int)) {
let i = vec::len(v);
while (i > 0u) {
i -= 1u;
act(v[i]);
}
}
To run such an iteration, you could do this:
# fn for_rev(v: [int], act: block(int)) {}
for_rev([1, 2, 3], {|n| log(error, n); });
Making use of the shorthand where a final closure argument can be moved outside of the parentheses permits the following, which looks quite like a normal loop:
# fn for_rev(v: [int], act: block(int)) {}
for_rev([1, 2, 3]) {|n|
log(error, n);
}
Note that, because for_rev()
returns unit type, no semicolon is
needed when the final closure is pulled outside of the parentheses.