rust/doc/tutorial/func.md
Marijn Haverbeke 0f72c53fdf Go over the tutorial again
Edit some things, make sure all code runs.
2012-01-12 13:19:02 +01:00

175 lines
6.1 KiB
Markdown

# 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
<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 to for spawning new
[tasks](task.html).
### 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.