Functions & closures
Define a useful interface, return results, and pass behavior to other code.
On this page
A function declares the types it accepts and the values it returns. Local variables inside its body can still use type inference.
Define and call a function#
fn line_total(price: int, quantity: int (1)) int {
return price * quantity
}
fn main() {
println(line_total(12)) // 12
println(line_total(12, 3)) // 36
}
The result type follows the parameter list. A function that only performs an action can omit its result type. A default argument goes in parentheses after its type, as in quantity: int (1).
Arguments are evaluated from left to right after the callable or method receiver. Prefer straightforward calls when arguments also change state.
Read command-line arguments#
Declare an Array[String] parameter on main to receive arguments:
fn main(args: Array[String]) {
let name = args.get(1) !? "Guest"
println("Welcome, %name")
}
Index zero is the executable's path. This program uses the first user-supplied argument as a name, or Guest if it is absent.
Pass a function#
A callback type uses fn(argument-types)(return-types). Here, fn(int)(int) accepts an integer and returns an integer:
fn apply_twice(value: int, transform: fn(int)(int)) int {
return transform(transform(value))
}
fn main() {
let increment = fn(value: int) int { return value + 1 }
println(apply_twice(3, increment)) // 5
}
An anonymous function uses the same fn syntax as a named one, but has no name. Its parameter and return types must match the callback signature; use a wrapper function when a conversion is needed.
Capture surrounding values#
A closure can use a value from the scope where it is created:
fn main() {
let prefix = "Hello"
let greet = fn(name: String) String {
return "%prefix, %name"
}
prefix = "Goodbye"
println(greet("Ada")) // Hello, Ada
}
The closure captures the current value when it is created. Reassigning prefix later does not update the captured value. Capturing an object captures a reference to that object, so mutations to the object can still be shared. See Memory & references.
Run cleanup before returning#
defer schedules a call for the end of the current function. Put it immediately after acquiring a resource so the cleanup is visible next to the acquisition:
let file = fs.stream("notes.txt") ! return
defer file.close() _
Deferred calls run in reverse order on a normal return or an error return. Their arguments are captured when the defer statement is reached. The _ above deliberately ignores a close error; when successful output matters, close explicitly and handle the result.
To declare a function that can fail, add an error type after its result type. That is covered in Error handling.