Guide 4 of 36
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
}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: "[email protected]")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 defaultDefault 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
}Closures Capture Variables¶
let multiplier = 3
let multiply = |x| x * multiplier // captures `multiplier`
print(multiply(5)) // 15Higher-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.
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.