Functions
fn fib(n: i64) -> i64 { if n < 2 { return n } return fib(n - 1) + fib(n - 2)}
fn greet(name: str) { // no `-> R`: returns unit print("hello, " + name)}Declarations
Section titled “Declarations”fn name(param: Type, ...) -> Result { body } declares a function. Every
parameter has a written type, and so does the result; without -> Result the
function returns unit. A function that returns a value must
return on every path.
Functions are module-level items, declared in any order: a function can call
one declared further down, and functions can be mutually recursive. They are
private to their module unless declared pub fn.
Between the signature and the body, requires and ensures clauses can state
a contract, which the compiler tries to break while compiling
and the program checks as it runs; nocopy says that natively the
function copies no list, string or closure; and replaces
makes it a rewrite rule that calls matching a pattern use instead,
once the compiler has checked it computes the same.
There is no overloading, no default arguments, and no methods: functions that
work on a type take it as a parameter, usually first (push(v, x); for a
struct to be changed, push(&v, x) in compiled code).
f(a, b) evaluates the arguments from left to right, then calls f.
Arguments are passed by value: a function gets its own copy of every struct and
array, so it cannot change the caller’s, and changing a parameter (n = n - 1)
changes only the function’s copy. To let a function change something, pass a
pointer in compiled code or a cell in hosted code.
The VM allows 10 000 nested calls; deeper recursion stops the program with
stack overflow. Compiled code is limited by the size of the OS stack.
Tail calls
Section titled “Tail calls”A function that returns a call, return g(...) with nothing done to the
result, does not nest: g takes the place of the caller, which is gone
before g runs, so a chain of such calls runs in constant stack however deep
it goes. A call of the function itself becomes a jump back to its start with
the new arguments; a call of another function, as in mutual recursion or a
state machine, is a tail call, whatever the two functions’ parameters.
fn count(n: i64, acc: i64) -> i64 { if n == 0 { return acc } return count(n - 1, acc + 1) // a jump: count(10000000, 0) is fine}
fn depth(n: i64) -> i64 { if n == 0 { return 0 } return 1 + depth(n - 1) // not a tail call: the result is used}
fn is_even(n: i64) -> bool { if n == 0 { return true } return is_odd(n - 1) // a tail call: is_even(10000000) is fine}
fn is_odd(n: i64) -> bool { if n == 0 { return false } return is_even(n - 1)}This is a rule of the language, the same on the VM, natively and
freestanding, at every -O level, not an optimization that may or may not
happen. It holds anywhere in the function: in either branch of an if, in a
match arm, inside a loop. Lists and strings passed along are moved into the
parameters or handed over to the callee, and natively whatever else the round made is freed before the
jump or tail call, so a tail call that would have had to free something after
returning is one all the same. Calls of C functions and of function values
(return f(x) with f a parameter) are ordinary calls, and so is every
return g(...) in a function with an ensures clause, which has to check
what g returned (Contracts).
Entry points
Section titled “Entry points”A program starts at main (hosted, native) or _start (freestanding), which
returns nothing or an i64 exit status. See Profiles.
Compile-time parameters
Section titled “Compile-time parameters”A parameter marked comptime is given at compile time, and makes the function
generic: it is compiled once for each distinct set of compile-time arguments.
fn max(comptime T: type, a: T, b: T) -> T { if a > b { return a } return b}
let m = max(u8, 3, 200)See Generics. A comptime parameter of a function type makes
calls to it direct, and lets a closure be
passed without the GC.
Functions as values
Section titled “Functions as values”A function’s name, used without calling it, is a value of type
fn(params) -> result that can be stored, passed and returned:
fn apply(f: fn(i64) -> i64, x: i64) -> i64 { return f(x)}
apply(fib, 10)See Function values and Closures, which
also covers anonymous functions (fn(x: i64) -> i64 { return x * x }).
Builtins
Section titled “Builtins”Some names, such as print, len and syscall, look like functions but are
built into the compiler: they may accept several types and cannot be used as
values. They are listed in Builtins.