Mutation and var paths
A change needs an unbroken var path from the binding down to the field being changed, and a var parameter or a var fn receiver is a reference that cannot outlive the call it belongs to.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Nothing in the language is changed through an alias. A change always goes through a var path: a var binding,
var parameter or a var fn receiver, then var fields, indices and ranges all the way down to the value that changes.
Example#
type Counter {
var count: Int = 0
var fn increment() {
count = count + 1
}
}
fn incrementTwice(var target: Counter) {
target.increment()
target.increment()
}
var counter = Counter()
incrementTwice counter
print counter.countSyntax#
<var-path> ::= (<var-binding> | <var-parameter> | "self" in a var-self member) <step>*
<step> ::= "." <var-field> | "[" <index-or-range> "]"Rules#
A
varparameter works on the caller's value, and its argument has to be avarpath.incrementTwicechangescounteritself; there is no marker at the call site, the parameter's ownvaris what promises it.Kompiliert nichttype Counter { var count: Int = 0 var fn increment() { count = count + 1 } } fn incrementTwice(var target: Counter) { target.increment() target.increment() } const frozen = Counter() incrementTwice frozen // error: `incrementTwice` changes `target`, and `frozen` is a `const`A path through
a[key]ora[from..to]changes the collection in place, without a copy.samples[1..4].sort { value => value }sorts that slice ofsamplesitself, the same way a field step would.A reference is second-class: it exists only as a
varparameter or avar fnreceiver, for the duration of one call. It cannot be stored in a field, returned, or captured by a closure that outlives the call - which is exactly what keeps the language free of lifetimes and dangling references. A closure may capture one only where it stands straight as the argument of a call whose parameter only calls it (rule 4), and the checker enforces it.Kompiliert nichttype Counter { var count: Int = 0 var fn watcher(): () => Void { { count = count + 1 } } } // error: This closure captures the receiver `self` and may outlive the callA captured
varbinding follows the same rule as a reference. The closure shares the binding with its scope rather than copying it, so it may only run while the binding exists: it may stand straight as the argument of a call whose parameter only calls it (items.forEach { total = total + _ }, a receiver closure, a block of a DSL), and it may not be bound to a name, stored, returned or handed to a parameter that keeps it. See Closures, rule 8.Kompiliert nichtfn counter(): () => Int { var count = 0 { count = count + 1 count } } // error: This closure captures the `var` binding `count` and may outlive itState that has to outlive its scope is a shared type, which says it has an identity; recursion is a local
fn, which captures nothing.The base of a var path can never be a temporary. Changing the result of a call directly is always rejected, because the change would be thrown away the moment the call returns - the value has to be bound to a name first.
Kompiliert nichttype Counter { var count: Int = 0 var fn increment() { count = count + 1 } } fn makeCounter(): Counter { Counter() } makeCounter().increment() // error: This is a temporary, and `increment` changes its receiver: the change would be thrown awayAs the argument of a
varparameter a temporary is fine: the callee becomes its only owner, so nothing is ever written back anywhere.tick(makeCounter())needs nothing bound first.The variable of a
forloop is aconst; the slot of afor varis a path.for var element in itemsbinds each slot of the container in turn, and every use ofelementis the accessitems[key]of the turn - see Changing elements in place. The variable of a plainforis a copy, so changing an element through it needs the path (items[index].x = 1) or a new collection built withmap.type Counter { var hits: Int } var counters = [Counter(0), Counter(5)] for var counter in counters { counter.hits = counter.hits + 1 } print counters // prints [Counter(hits: 1), Counter(hits: 6)]
What this is not#
Taking a value out of a collection is not a way to reach its path. It is a copy, so changing it changes nothing
the collection holds - this is the copy trap, and reading counters[0] again is the only way to see whether a change
went where it was meant to.
var counters = [Counter(), Counter()]
counters[0].increment() // Through the path: changes the element in place
print counters[0].countvar counters = [Counter(), Counter()]
var first = counters[0] // A copy
first.increment() // Changes the copy. `counters[0]` is still at 0.
print counters[0].countRelated#
- Exclusivity - the rule for when two
varaccesses to the same path may not overlap. - Fields - which field modifier a path is allowed to end on.
- Declaring a type -
var fnon a verb, and its participle that needs no path at all. - Bindings -
constandvaron the binding a path starts from.