Function
func declares a function. The return annotation is optional; omit it and the function returns void. When a function returns a value you must write -> T, and the type returned by return is checked strictly against the declaration.
Basic Forms
func Add(a: int, b: int) -> int
{
return a + b;
}
func Dbl(n: int) -> int => n * 2;
func Fib(n: int) -> int
{
if n < 2
{
return n;
}
return Fib(n - 1) + Fib(n - 2);
}- The expression body
=> expr;is the compact form for single-expression functions; its semantics match the block body. - Recursion calls the function by name directly;
Fib(10)above yields 55. - Return-type checking on block bodies is strict in both directions: declaring
-> intwhile missing a return, or returning the wrong type, fails to compile.
Parameter Modifiers
A modifier in the first position of a parameter determines how it is passed:
| Modifier | Name | Semantics |
|---|---|---|
| (none) | default | passed immutably, like val |
val | immutable | cannot be assigned inside the body |
var | mutable | the parameter is a copy of the argument; the body may change it without affecting the caller |
in | read-only | a read-only view; avoids copying large objects |
ref | write-back | parameter and argument share one storage; writes in the body reach the argument |
out | write-only | no value needed on entry; meant for carrying a result back out |
params | variadic | the last parameter accepts any number of arguments; the body sees an array |
Call sites must write the ref and out modifiers explicitly:
func Swap(ref a: int, ref b: int)
{
val t = a;
a = b;
b = t;
}
func Divide(a: int, b: int, out remainder: int) -> int
{
remainder = a % b;
return a / b;
}
var x = 1;
var y = 2;
Swap(ref x, ref y);
print(x, y);
var rem = 0;
print(Divide(7, 2, out rem), rem);2 1
3 1After Swap(ref x, ref y), x and y have swapped; Divide(7, 2, out rem) returns the quotient 3, and rem carries back the remainder 1.
Default Parameters
Parameters can carry default values, which must be compile-time constants. A call may omit the argument for any parameter that has one:
func Greet(name: string, greeting: string = "hello", punct: string = "!") -> string
{
return $"{greeting}, {name}{punct}";
}
print(Greet("world"));
print(Greet("world", "good morning"));hello, world!
good morning, world!Named Arguments
Arguments can be written name: value, binding to the parameter slot by name: order becomes free, parameters with defaults can be skipped, and positional arguments must come before named ones:
print(Greet("world", punct: "."));
print(Greet(greeting: "good night", name: "moon"));hello, world.
good night, moon!params and Spread Calls
params marks the last parameter; it accepts any number of arguments, and the body sees a fixed-length array. To hand a whole collection to a params parameter, use a spread call: write ... plus the collection name in the argument list, and the collection flattens element by element:
func Total(params nums: int[]) -> int
{
var total = 0;
for n in nums
{
total += n;
}
return total;
}
val xs: list<int> = [4, 5];
print(Total(1, 2, 3));
print(Total(...xs));
print(Total(1, ...xs));6
9
10Total(...xs) spreads the list element by element, equivalent to Total(4, 5); literals spread too (Total(...[7, 8])).
A spread does not have to own the argument list. The positional arguments of a params call take the same shape as a literal for the params array, isomorphic to a list literal like [1, ...xs]: elements spread into whichever position ...xs occupies — Total(1, ...xs) is legal as written and equivalent to Total(1, 4, 5). Spreads can sit in the middle, and there can be several (Total(...a, 2, ...b)); the collection’s element type must match the params element type:
val head: list<int> = [1, 2];
val tail: list<int> = [8, 9];
print(Total(...head, 5, ...tail));25Fixed parameters consume positional arguments first: positional arguments fill fixed parameters in declaration order, and the overflow joins the spreads in the params array. Positional arguments after a spread still fill fixed slots in declaration order — in Join(...xs, "a"), "a" is the first positional argument and lands in sep:
func Join(sep: string, params items: int[]) -> string
{
var s = "";
var first = true;
for n in items
{
if (!first) { s = s + sep; }
s = s + n.ToString();
first = false;
}
return s;
}
val xs: list<int> = [4, 5];
print(Join(";", 1, ...xs));
print(Join(...xs, "a"));1;4;5
4a5Named arguments never spread: they bind to their own parameter slot by name, while positional arguments and spreads flatten as usual — in Join(sep: "-", ...xs), xs still spreads into items. Every argument is evaluated exactly once, in source order.
Overloads
Functions with the same name form an overload set; calls resolve by argument types and pick the best match:
func Show(v: int) -> string => $"int {v}";
func Show(v: string) -> string => $"string {v}";
print(Show(7), Show("seven"));int 7 string sevenDisambiguation works by scoring tiers (exact > assignable > promotion > boxed); candidates omitting fewer trailing arguments win, and fixed-arity forms beat params spreads.
Generics and where
Type parameters go after the name, constraints in a where clause. The clause order is fixed: uses → fail → where, then ->:
import Math.*;
func Bigger<T>(a: T, b: T) where T: Ordered -> T
{
if a < b
{
return b;
}
return a;
}
print(Bigger(4, 9));9Ordered is a protocol from std Math (ordered comparison); built-in scalars satisfy it implicitly, and inside the generic body a < b type-checks against the constraint protocol’s operator signature. Writing a protocol name bare in type position requires import Math.*; to activate it.
Named Functions as Values
When a function name appears in value position (as an argument, in an assignment, as a return value, stored into a container), it materializes into a function value whose type is a function type:
func On(n: int) -> void => print("on", n);
val f: func(int) -> void = On;
f(4);
val g = On;
g(5);on 4
on 5Functions from the same declaration materialize as the same value (a singleton); each evaluation of a lambda produces a new instance — see Lambdas and Closures. == judges by this instance identity: same singleton, equal; different instances, not equal — see Equality and Hashing.
Notes
- A top-level func is a module member; visibility is managed by the module system — see the part on Organizing Code. Methods on types take a
selfreceiver — see the Types part. - For
async funcsee the Async part; this page says no more about it.
