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.

Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite

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 quoted

Rules#

  1. A plain parameter is a value, evaluated once at the call site, before the body runs. This is the default and needs no keyword.

  2. var name: Type asks 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; changing name inside 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 count
  3. A 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).

  4. A closure parameter whose function type names its first parameter self is 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 config
  5. lazy Type accepts 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())
  6. Expression<Type> type checks the argument as an ordinary Type and additionally hands over its typed expression tree. The call site looks exactly like a call to Type itself; 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
}
Kompiliert nicht
fn makeIncrementer(var target: Int): () => Void {
  {
    target = target + 1
  }
}
// error: This closure captures the `var` parameter `target` and may outlive the call

A 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.

Esc