Skip to Content

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 -> int while 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:

ModifierNameSemantics
(none)defaultpassed immutably, like val
valimmutablecannot be assigned inside the body
varmutablethe parameter is a copy of the argument; the body may change it without affecting the caller
inread-onlya read-only view; avoids copying large objects
refwrite-backparameter and argument share one storage; writes in the body reach the argument
outwrite-onlyno value needed on entry; meant for carrying a result back out
paramsvariadicthe 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 1

After 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 10

Total(...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));
25

Fixed 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 4a5

Named 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 seven

Disambiguation 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));
9

Ordered 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 5

Functions 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 self receiver — see the Types part.
  • For async func see the Async part; this page says no more about it.
Last updated on October 11, 2026