Guide 11 of 53
Functions
Functions, closures, generators and async functions.
On this page
Basic Functions¶
fn greet(name: str) -> str {
"Hello, {name}!"
}
fn add(a: int, b: int) -> int {
a + b // implicit return — last expression is the return value
}Expression bodies¶
A function or method with one expression may use =:
fn double(x: int) -> int = x * 2
fn absolute(x: int) -> int = if x < 0 { -x } else { x }
fn identity<T>(value: T) -> T = valueThis has the same return value as a braced body whose last expression has no semicolon. An optional semicolon after the expression separates declarations; it does not discard the function's result. A multiline expression and a block expression also work. Use a braced body when writing several statements.
Return inference, defaults, generics, effects and cleanup follow the ordinary function rules. A Result function still constructs its successful value:
fn checked_double(value: int) -> Result<int, str> = .Ok(read_positive(value)? * 2)See the runnable example.
Conditions on entry and return¶
Use requires for an input condition and ensures for a condition on the
normal return value. The name result refers to that value in ensures:
fn remaining_stock(available: int, requested: int) -> int
requires available >= 0
requires requested >= 0 && requested <= available
ensures result >= 0 && result + requested == available
{
if requested == 0 { return available }
available - requested
}
assert(remaining_stock(12, 5) == 7)Every condition must be Boolean. They run once per call, in header order,
with all requires clauses before ensures. A false condition raises an
error naming the function, its authored condition and source location.
Tail expressions, explicit return and propagation with ? reach ensures
after the body's cleanup. Result::Err is a normal return; panics and failed
tasks skip postconditions. In an async fn, the postcondition checks the
completed value before the Future completes. Conditions can read parameters
and outer bindings, but cannot read variables declared inside the body.
Conditions cannot return or propagate out of the checked function. Use an
inner callback or attempt when a condition needs its own control flow.
An explicit -> void keeps its unit return; result is nil for its checks.
Methods and trait default bodies support contracts. Contracts on declarations
without a body and on generators are rejected. Async checked functions are
unsupported at comptime.
See the runnable example.
Explicit Return¶
fn absolute(x: int) -> int {
if x < 0 {
return -x
}
x
}No Return Type¶
Functions that don't return a value omit the -> Type:
fn say_hello(name: str) {
print("Hello, {name}!")
}Parameters¶
Type Annotations¶
fn process(x: int, y: f64, name: str) -> str {
"{name}: {x}, {y}"
}Named Arguments¶
fn create_user(name: str, age: int, email: str) -> User {
User { name, age, email }
}
// Call with named arguments
create_user(name: "Alice", age: 30, email: "alice@example.com")When the local value has the same name as the argument label, write name::
let name = "Alice"
let age = 30
let email = "alice@example.com"
create_user(email:, name:, age:) // email: email, name: name, age: ageShorthand works in function and method argument lists and can be mixed with explicit values. It reads the variable in the caller's lexical scope; it does not request a default value. Reordering, defaults and validation follow the ordinary named-argument rules. Direct decorator arguments still require an explicit value.
Function types can also expose a named-call interface. This is useful for callbacks and Verniz components:
fn TodoList(items: [Todo], row: fn(item: Todo) -> View) -> View {
<ul>{for todo in items { <row item={todo} /> }}</ul>
}The label is erased after named arguments are reordered, so it does not change
type compatibility: a function declared as fn todo_row(todo: Todo) -> View
can be passed where fn(item: Todo) -> View is expected. The older
fn(Todo) -> View spelling remains valid and positional-only. A function type
must use names for every parameter or for none of them.
Method calls accept named arguments too. Named arguments are resolved before
types are known, so the method name selects the signature: when exactly one
impl in the program (or in the standard library) declares a method of that
name whose parameters cover every label used, the call is reordered like a
free function and omitted defaults are filled in. If several declarations
with different parameter lists match, the call is ambiguous and reported as
TE131; call it positionally. A method nobody declares is left untouched.
impl Box {
fn scale(self, by: int = 1, plus: int = 0) -> int { self.n * by + plus }
}
b.scale(plus: 4, by: 2) // by = 2, plus = 4
b.scale(plus: 1) // by keeps its defaultPublic labels and internal names¶
Use two names when the call reads better with a label that differs from the name used in the body:
fn distance(from start: int, to finish: int) -> int = finish - start
distance(from: 2, to: 8)The first name is the public argument label; the second is the local parameter.
distance(2, 8) remains valid. Calling with start: is an unknown label.
A single-name parameter retains its existing public label and local binding.
Defaults and captures use the internal name, while named puns read the caller:
fn advance(from start: int, to finish: int = start + 2) -> int = finish - start
let from = 4
advance(from:) // the caller supplies from; finish defaults using startMethods, generic functions and labelled callback types follow the same rules.
A named trailing block selects the public label: dialog() confirm { save() }
can call a parameter declared as confirm action: fn() -> int.
An explicitly labelled function type supplies the callable value's public
interface. Renaming an explicit public label updates its call sites; renaming
the internal name updates its body and default references.
See the runnable example.
Default Values¶
Parameters can have explicit defaults, and name?: T is the concise form for
an optional parameter whose default is nil:
fn Card(children?: fn() -> View) -> View {
<section>{children?.()}</section>
}The declarator makes the parameter itself optional. This distinction matters
for function types: callback?: fn() -> View is an optional function, while
callback: fn() -> View? is a required function with an optional return.
callback?.(args) evaluates the callback once. When it is nil, it returns
nil without evaluating args; otherwise it calls the value directly. The
spelling callback?() keeps the existing ? propagation semantics and is not
an optional call.
Lambdas / Closures¶
Lambdas use the |params| expr syntax:
// Single expression
let double = |x| x * 2
let add = |a, b| a + b
// With type annotations
let square = |x: int| -> int { x * x }
// Multi-line body
let process = |x: int| -> int {
let y = x * 2
y + 1
}Short Lambdas¶
A proved unary callback argument can omit the parameter declaration:
let adults = users.filter(_.active && _.age >= 18)
let amounts = items.map(_.price * _.quantity)
let adjusted = items.map(_.price + adjustment)
let doubled = numbers.map(_ * 2)
let unchanged = numbers.map(_)
let names = users.map(format_name(_.name))
let lengths = groups.map(_.map(_ + 1).len())Every _ in the callback's expression refers to the same parameter, including
ordinary value arguments passed to nested calls. Normal local variables such as
adjustment are captured. A nested callback position establishes its own
parameter: the inner _ + 1 above receives one group element, while the outer
_ receives the group. Explicit lambdas, blocks, collections, control flow and
pipes keep their authored boundaries.
Scalar and identity shorthand require an actual expected unary callable type;
an ordinary value parameter or unresolved dynamic call does not become a
callback merely because its argument contains _. Access-based shorthand
remains available for existing untyped callback calls. Pipe slots and discard
bindings keep their existing meaning. Use an explicit lambda to name multiple
callback parameters or write a block body.
Closures Capture Variables¶
let multiplier = 3
let multiply = |x| x * multiplier // captures `multiplier`
print(multiply(5)) // 15Named callback blocks¶
Several callbacks can follow a call, using their parameter names:
fn dialog(title: str, content: fn() -> str, confirm: fn() -> bool) -> str {
let message = content()
if confirm() { "{title}: {message}" } else { "{title}: cancelled" }
}
let answer = dialog(title: "Delete?") content {
"This removes one file."
} confirm {
true
}This supplies content: || { ... } and confirm: || { ... }. Each body runs
when dialog invokes that callback. A block with parameters writes them
inside its braces: transform(3) next { |value| value * 2 }.
Labels refer to callable parameters of the actual target; ordinary named argument rules still reject unknown, duplicate or already supplied labels. The call evaluates the target and creates arguments in the order written, even when the labels appear in a different order from the declaration. Captures, optional callbacks, return types, effects and cleanup retain their ordinary lambda rules.
Start each label on the line containing the preceding ) or }; the
body can span several lines. A semicolon or a new line before the label
ends attachment. Bare Point { ... } keeps its struct literal meaning.
Words used as operators, such as with, as, is and in, keep
their existing meaning; pass callbacks with those labels inside parentheses.
Named callbacks retain ordinary lambda behavior, including a parameter
named children; their spelling does not enable view collection.
See the runnable example.
Higher-Order Functions¶
Functions can take functions as parameters and return functions:
fn apply(f: fn(int) -> int, x: int) -> int {
f(x)
}
fn make_adder(n: int) -> fn(int) -> int {
|x| x + n
}
let add5 = make_adder(5)
print(add5(10)) // 15Methods¶
Methods are defined inside impl blocks. See Data Structures for details.
struct Circle {
radius: f64,
}
impl Circle {
fn area(self) -> f64 {
3.14159 * self.radius * self.radius
}
fn new(radius: f64) -> Circle {
Circle { radius }
}
}
let c = Circle { radius: 5.0 }
print(c.area())Decorators¶
Functions can be annotated with decorators:
@test
fn test_addition() {
assert_eq(2 + 2, 4, "basic addition")
}
@memoize
fn fibonacci(n: int) -> int {
if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) }
}
@deprecated("use new_function() instead")
fn old_function() {
// ...
}See Decorators for the full list.
Related examples and reference¶
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.