Guide 12 of 53
Function parameters and lambdas
Lambdas, labels, callback blocks and pattern clauses.
On this page
Lambdas, labels, callback blocks and pattern clauses.
Predicate short lambdas¶
Accesses rooted in underscore refer to the same implicit parameter. Combine fields, arithmetic and boolean tests in a callback; use an explicit lambda when the scope would be ambiguous.
Practice in By Example · Source program
// Short lambdas share one parameter across the argument's rooted accesses.
struct User { name: str, age: int, active: bool }
struct Line { price: int, quantity: int }
fn main() {
let users = [
User { name: "Ada", age: 36, active: true },
User { name: "Bo", age: 17, active: true },
User { name: "Cy", age: 24, active: false },
]
print(users.filter(_.active && _.age >= 18).map(_.name))
// expected: [Ada]
let lines = [Line { price: 3, quantity: 4 }, Line { price: 5, quantity: 2 }]
print(lines.map(_.price * _.quantity))
// expected: [12, 10]
let adjustment = 2
print(lines.map(_.price + adjustment))
// expected: [5, 7]
}Named argument shorthand¶
Calling with name: forwards the caller's name binding. Explicit arguments and shorthand can be mixed and reordered by label. A missing local binding is an error; the formatter preserves the shorthand.
Practice in By Example · Source program
// Named argument shorthand keeps the label and uses the local value.
struct User {
name: str,
email: str,
active: bool,
}
fn create_user(name: str, email: str, active: bool = true) -> User {
User { name, email, active }
}
struct Team {
title: str,
}
impl Team {
fn contact(self, name: str, email: str) -> str {
"{self.title}: {name} <{email}>"
}
}
fn main() {
let name = "Ana"
let email = "[email protected]"
// Reordering and defaults are the same as for explicit named arguments.
let user = create_user(email:, name:)
print("{user.name} <{user.email}>") // expected: Ana <[email protected]>
print(user.active) // expected: true
// Explicit values and shorthand can be mixed.
let disabled = create_user(name:, email:, active: false)
print(disabled.active) // expected: false
let team = Team { title: "Zolo" }
print(team.contact(email:, name:)) // expected: Zolo: Ana <[email protected]>
}Expression function bodies¶
Use = expression for a small function or method. The expression supplies the return value, while return, Result propagation, generics and defaults keep the same function semantics as a block body.
Practice in By Example · Source program
// Feature: Expression-bodied functions and methods
// Syntax: fn name(parameters) -> Type = expression
// When to use: a small calculation, branch, forwarding call or method.
fn double(x: int) -> int = x * 2
fn absolute(x: int) -> int = if x < 0 { -x } else { x }
fn increment(value: int = 3) -> int = value + 1;
fn identity<T>(value: T) -> T = value
struct Counter(value: int) {
fn next(self) -> int = self.value + 1
}
fn read_positive(value: int) -> Result<int, str> =
if value > 0 { .Ok(value) } else { .Err("positive value required") }
// Question-mark failures exit this function; success is explicitly wrapped.
fn checked_double(value: int) -> Result<int, str> = .Ok(double(read_positive(value)?))
print(double(4)) // expected: 8
print(absolute(-7)) // expected: 7
print(increment()) // expected: 4
print(identity("Ada")) // expected: Ada
let counter: Counter = .{ value: 9 }
print(counter.next()) // expected: 10
match checked_double(5) {
.Ok(value) => print(value),
.Err(error) => print(error)
} // expected: 10
match checked_double(-1) {
.Ok(value) => print(value),
.Err(error) => print(error)
} // expected: positive value requiredNamed trailing blocks¶
Several callback arguments can follow a call as labelled blocks. Labels resolve to callable parameters, so blocks can appear in another order. Creating a callback does not execute its body; mutable captures keep their existing cells.
Practice in By Example · Source program
// Several callbacks read in the order of the operation they describe.
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
}
print(answer)
// expected: Delete?: This removes one file.
fn transform(value: int, before: fn(int) -> int, finish: fn(int) -> int) -> int {
finish(before(value))
}
print(transform(3) finish { |value| value * 2 } before { |value| value + 1 })
// expected: 8
// Creating a callback does not run its body. Mutable captures keep their cell.
var calls = 0
fn invoke(work: fn() -> int) -> int = work()
print(invoke() work { calls += 1; calls })
// expected: 1
print(calls)
// expected: 1External argument labels¶
Separate a public call label from the local parameter name: from source: int. The caller uses from:, while the function reads source. Labels are part of callable interfaces and remain available when passing a function as a value.
Practice in By Example · Source program
// expected: 12
// expected: 3
// expected: 15
// expected: 9
// expected: 18
// expected: 2
fn travel(from source: int, to destination: int, by step: int = 1) -> int =
(destination - source) * step
let from = 2
let to = 8
print(travel(to:, from:, by: 2))
print(travel(1, 4))
fn choose(with transform: fn(int) -> int, on value: int) -> int = transform(value)
print(choose(on: 5, with: |value| value * 3))
fn dialog(confirm action: fn() -> int, title heading: str = "Ready") -> int =
action() + heading.len()
print(dialog(title: "Go") confirm { 7 })
fn double(value: int) -> int = value * 2
let labelled: fn(input: int) -> int = double
print(labelled(input: 9))
fn advance(from start: int, to finish: int = start + 2) -> int = finish - start
print(advance(from: 4))Pattern function clauses¶
Consecutive declarations of the same function select a body by parameter patterns and optional guards, in written order. Clauses form one callable and must be exhaustive; guarded clauses alone do not establish coverage. Contracts and public labels must agree.
Practice in By Example · Source program
// Consecutive clauses form one function and try patterns in written order.
fn classify(0: int) -> str = "zero"
fn classify(n: int) if n > 0 -> str = "positive"
fn classify(n: int) -> str = "negative"
// Public argument labels are shared; binders remain local to each clause.
fn magnitude(value n: int) if n < 0 -> int = -n
fn magnitude(value n: int) -> int = n
print(classify(0))
print(classify(3))
print(classify(-3))
print(magnitude(value: -4))
// expected: zero
// expected: positive
// expected: negative
// expected: 4Complete unary short lambdas¶
With a proven one-argument callback signature, underscore can stand for the whole parameter, including arithmetic and ordinary nested value calls. Nested callback slots get their own parameter. Blocks, explicit lambdas, collections and pipes remain scope boundaries.
Practice in By Example · Source program
// expected:
// 4
// 6
// Ana!
// 2
// 1
struct User { name: str }
fn format_name(name: str) -> str { name + "!" }
let doubled = [2, 3].map(_ * 2)
print(doubled[0])
print(doubled[1])
print([User { name: "Ana" }].map(format_name(_.name))[0])
// Each proved nested callback gets its own parameter.
let sizes = [[2, 3], [4]].map(_.map(_ + 1).len())
print(sizes[0])
print(sizes[1])Required named arguments¶
Parameters after * must be supplied by their external labels. Defaults can still be omitted. This contract follows methods, exported declarations and typed function values; passing the function as a callback does not remove the requirement.
Practice in By Example · Source program
// Required names make call sites readable, including through a function value.
fn transfer(*,from:int,to:int,amount:int=1)->int=from-to+amount
let send=transfer
print(send(from:10,to:3,amount:2))
// Earlier parameters may remain positional; defaults still belong to the callee.
fn advance(start:int,*,by:int=1)->int=start+by
print(advance(7,by:2))Anonymous match callbacks¶
A match { pattern => value, ... } expression creates a unary function. Its input type comes from the callback context; arm bindings, guards and exhaustiveness use the ordinary match rules.
// Pass a complete decision as a callback.
enum Reading { Waiting, Measured(int) }
fn show(reading: Reading, describe: fn(Reading) -> str) -> str {
describe(reading)
}
print(show(.Waiting, match {
.Waiting => "waiting",
.Measured(value) => "{value} units",
})) // expect: waiting
print(show(.Measured(12), match {
.Waiting => "waiting",
.Measured(value) => "{value} units",
})) // expect: 12 unitsExplicit partial application¶
partial f(fixed, _) creates a function with one positional parameter for each underscore, in written order. The callee or receiver and fixed arguments are evaluated once when the function is created. Named labels still refer to the original callable.
// Fixed arguments are acquired once; each underscore is an input of the new function.
var acquisitions = 0
fn initial() -> int { acquisitions += 1; 10 }
fn add(base: int, amount: int) -> int { base + amount }
let add_initial = partial add(initial(), _)
print(add_initial(5)) // expect: 15
print(add_initial(8)) // expect: 18
print(acquisitions) // expect: 1
fn subtract(left: int, right: int) -> int { left - right }
let remaining = partial subtract(right: _, left: 20)
print(remaining(3)) // expect: 17Explicit capture initializers¶
capture { alias: expression } |parameter| body evaluates initializers once, from left to right. Each alias is local to the constructed closure. Other free variables follow the usual closure rules.
// The closure uses the price chosen at construction, even after it changes.
var unit_price = 5
let quote = capture { price: unit_price } |quantity: int| price * quantity
unit_price = 9
print(quote(3)) // expect: 15
print(unit_price) // expect: 9Continue learning¶
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.