Parameter modes
A parameter is an ordinary value unless it says otherwise; var hands over a path to mutate, lazy defers evaluation once, a self-named closure resolves names against a receiver, and Expression also hands over the typed tree.
Most parameters are ordinary values, evaluated once at the call site before the function runs. A parameter can ask for something else instead: a path to mutate, an expression evaluated only if it is needed, or a closure whose names resolve against a receiver.
Example#
fn reset(var counter: Int): Int {
counter = 0
counter
}
fn orElseComputed(value: Int?, fallback: lazy Int): Int {
value ?? fallback
}
var n = 5
print reset(n)
print n
print orElseComputed(1, 999)Syntax#
<name>: <Type> an ordinary parameter
var <name>: <Type> a var path
<name>: () => <Type> a closure
<name>: (self: <Receiver>) => <Type> a receiver closure
<name>: lazy <Type> deferred, at most once
<name>: Expression<<Type>> deferred and quotedRules#
A plain parameter is a value, evaluated once at the call site, before the body runs. This is the default and needs no keyword.
var name: Typeasks for a var path, and the function works on the caller's value. The caller passes a binding, field or index that can be changed, not a copy; changingnameinside the function changes what the caller holds (see Bindings).fn increment(var target: Int) { target = target + 1 } var count = 0 increment count increment count print countA parameter typed as a function is a closure, and one written after the call as a trailing closure if it is the last parameter (see Closures and Trailing closures).
A closure parameter whose function type names its first parameter
selfis a receiver closure. Inside its body, names resolve against that receiver first, exactly as inside a method, which is what makes a configuration block or a builder read as its own small language.type ServerConfig { var host: String = "localhost" var port: Int = 8080 } fn server(configure: (var self: ServerConfig) => Void): ServerConfig { var config = ServerConfig() configure config config } const config = server { host = "0.0.0.0" port = 8080 } print configlazy Typeaccepts any expression of that type and evaluates it at most once, the first time the parameter is read. A parameter that is never read never runs its argument at all, which is what makes a fallback (value ?? expensiveDefault()) cheap on the common path.fn expensive(): Int { print "computing" 99 } fn orElseComputed(value: Int?, fallback: lazy Int): Int { value ?? fallback } print orElseComputed(1, expensive()) print orElseComputed(None, expensive())Expression<Type>type checks the argument as an ordinaryTypeand additionally hands over its typed expression tree. The call site looks exactly like a call toTypeitself; see Quoted expressions for what the tree contains and what can stand in it.
What this is not#
var on a parameter is not a reference type, and it is not inout you can store. It exists only for the
duration of the call: it cannot be put in a field, returned, or captured by a closure that is stored (see
Closures), which is why there are no lifetimes to annotate and nothing can dangle.
fn increment(var target: Int) {
target = target + 1
}fn makeIncrementer(var target: Int): () => Void {
{
target = target + 1
}
}
// error: This closure captures the `var` parameter `target` and may outlive the callA receiver closure is not a method added to Receiver. It is an ordinary closure value passed as an argument;
Receiver never declares it, and two receiver closures for the same type can disagree freely, one per builder
function.
Related#
- Closures - the closure form every function-typed parameter takes.
- Trailing closures - writing the last closure parameter after the call.
- Quoted expressions - what
Expression<Type>hands the function beyond the value. - Declaring a function - the rest of a parameter's declaration.