fn declares a callable unit. The same keyword introduces free functions,
methods, static functions, trait requirements, and low-level prelude
signatures.
fn add(a: int, b: int): int {
return a + b
}Function and method names use snake_case.
Bodies
A block body can contain statements, early returns, and a final tail expression.
fn absolute(value: int): int {
if value < 0 {
return -value
}
value
}For a single expression, use =>.
fn double(value: int): int => value * 2An expression body is checked against the declared return type just like a block’s tail expression.
Parameters
Parameters have names and types. Calls are currently positional.
fn clamp(value: int, minimum: int, maximum: int): int {
if value < minimum { return minimum }
if value > maximum { return maximum }
return value
}
bounded := clamp(input, 0, 100)Named call arguments from the older function design are not part of the current call AST. Variadic parameters are also not implemented.
Defaults
A trailing parameter may have a compile-time default value. Once one parameter has a default, every following parameter must also have one.
fn connect(host: string, port: int = 5432, secure: bool = true): void {
println("$host:$port secure=$secure")
}
connect("db.internal")
connect("db.internal", 6432)
connect("db.internal", 6432, false)Omitted arguments are filled from left to right. Because calls are
positional, a caller cannot skip port and supply only secure.
Defaults must fold at compile time. They may use literals and constants but cannot read a caller’s runtime state.
Mutable parameters
Prefix a parameter with mut when the body assigns to the parameter or
mutates through it.
fn increment(mut value: int): int {
value += 1
return value
}For a parameter whose mutation is visible through the caller’s place, the argument must be a mutable binding. A literal or immutable binding cannot satisfy that call contract.
Parameter decorators such as the compiler-internal @borrow can refine
lowering behavior. They are advanced contracts, not a replacement for mut.
Returns
Write : T after the parameter list to declare a return type.
fn answer(): int {
return 42
}void is the source-level spelling for a function with no useful return
value. Unit remains present in internal stubs and older fixtures.
fn log_ready(): void {
println("ready")
}If the return annotation is omitted, the semantic checker infers a compatible return from explicit returns and tail expressions. Public functions should normally state their result type so changes to the body do not silently change the API.
Multiple incompatible success returns can infer an anonymous union. Prefer an explicit return type at an exported boundary.
Errors
A fallible function writes T ! E.
fn parse_port(text: string): int ! ParseError {
return parse_int(text)?
}The function’s success type is T, while calls without propagation produce
Result[T, E]. Error declarations, ?, and catch are covered under
Errors.
Calls
A call evaluates its callee and arguments, then checks each argument against the corresponding parameter.
total := add(20, 22)Methods use member-call syntax.
length := name.len()Generic arguments are usually inferred. When they must be explicit, place square-bracket type arguments immediately before the call parentheses.
bytes := List.new[byte]()
size := size_of[Packet]()
converted := value.convert[Target]()This call form is deliberately distinguishable from indexing because it must
be followed by (...).
Generics
Function type parameters use square brackets.
fn identity[T](value: T): T => value
fn larger[T](a: T, b: T): T where T: Comparable[T] {
if a > b { a } else { b }
}Bounds may be written inline or in a where clause. Generic resolution,
inference, and specialization are detailed in
Generics.
Methods
A method declares self or mut self as its receiver.
struct Counter {
value: int
fn current(self): int => self.value
fn increment(mut self): void {
self.value += 1
}
}A function inside a struct without self is static and is called through the
type.
struct Point {
x: float
y: float
fn zero(): Point => Point { x: 0.0, y: 0.0 }
}
origin := Point.zero()Receiver-prefix declarations attach a function outside the type body.
fn Counter.reset(mut self): void {
self.value = 0
}An impl block can also supply inherent or trait methods. See
Implementations.
Function values
fn(A, B) -> R describes a function value.
fn apply(value: int, operation: fn(int) -> int): int {
return operation(value)
}
result := apply(21, double)A function type may be fallible and may state an effect row.
type Loader = fn(string) -> Data ! LoadError
type Work[T, E] = fn() -> T using [E]Closures also inhabit function types and are documented separately in Closures.