Exclusivity
Two var accesses of the same call may never target the same path, so swap(a, a) and two indices the checker cannot tell apart are both compile errors, and items.swapAt is the one access that is allowed instead.
Not built natively yet. A closure that changes a top-level var while a function holds another one as a var
parameter is not built by the native back end yet, so torb run refuses the examples here that use it. torb check
accepts them, and the rules are the language's.
CONCEPT.md describes a rule beyond the single-path checks of var paths: two var accesses are never
allowed to run at the same time against the same path, even when they belong to one call rather than to two nested
ones. The checker enforces it, at the top level of a file included.
Example#
var items = [3, 1, 2]
const i = 0
const j = 1
items.swapAt i, j
print itemsSyntax#
<call>(<var-argument>, ...) // The access of the call begins once every argument above has been evaluatedRules#
The access of a call begins only once all of its arguments have been evaluated. Everything an argument reads has therefore already finished by the time the access starts, so
items.removeAt(items.length() - 1)type-checks as ordinary code: the length is read before anything is removed.var items = [3, 1, 2] items.removeAt(items.length() - 1) print itemsTwo
varaccesses of the same call may not target the same path. Passing one path to twovarparameters of one call (swap(a, a)), or two indices of one collection that the checker cannot tell apart (swap(items[i], items[j])), are both rejected.items.swapAt(i, j)is the one exclusivevaraccess the standard library offers instead of either.Does not compilefn swap<Value>(var first: Value, var second: Value) { const value = first first = second second = value } var a = 1 swap(a, a) print a // error: `a` is being changed by `swap` right nowTwo indices of one collection are rejected even when nothing but their value tells them apart, because the checker never evaluates a
constindex to compare it:items[i]anditems[j]overlap as far as it can tell, whether or notiandjhappen to differ at run time.Does not compilefn swap<Value>(var first: Value, var second: Value) { const value = first first = second second = value } var pair = [1, 2] swap(pair[0], pair[0]) print pair // error: `pair[...]` is being changed by `swap` right nowA closure argument runs inside the access of its call, so it may not reach the path the call is changing. The call's access has begun by the time the closure runs, which is what makes changing
listinsideapply list { ... }an error while two different fields of one value are fine. The same holds for a closure that captures avarbinding: it may only be passed directly as an argument (see var paths rule 4), so the call it is passed to is where the checker sees every access it makes.fn apply(var target: List<Int>, action: () => Void) { action() target.append 1 } var list = [1, 2] var log: List<String> = [] apply list { log.append "applied" } print list print logDoes not compilefn apply(var target: List<Int>, action: () => Void) { action() target.append 1 } var list = [1, 2] apply list { list.append 3 } print list // error: `list` is being changed by `apply` right nowitems.update(0) { ... }holds avaraccess toitemsfor as long as the closure runs, and a closure that changesitemsitself is the second access. Different fields are fine; the same path, a path above or below it are not.Does not compilevar items = [1, 2, 3] items.update(0) { value => items.append 4 value = value + 1 } print items // error: `items` is being changed by `update` right nowThis is checked where the closure is written, and it can be, because a closure that captures a
varbinding never gets anywhere else: it is not bound to a name, stored or returned, and it is only handed to a parameter that just calls it (Closures, rule 8). Soconst clear = { items = [] }followed byapply(items, clear)is refused whereclearis bound, before the call could run it inside its own access.
What this is not#
items.swapAt(i, j) is not the same call as swap(items[i], items[j]). swapAt takes both indices as plain
Int arguments and reaches into items itself, so there is only ever one var access - of items, not of two
places inside it - and no overlap to reject.
var items = [3, 1, 2]
const i = 0
const j = 1
items.swapAt i, j
print itemsfn swap<Value>(var first: Value, var second: Value) {
const value = first
first = second
second = value
}
var items = [3, 1, 2]
const i = 0
const j = 1
swap(items[i], items[j])
print items
// error: `items[...]` is being changed by `swap` right nowRelated#
- Mutation and var paths - the single-path rules that this rule builds on.
- Declaring a type -
var fn, the receiver form this rule is meant to protect. - Why values instead of references - the argument this rule serves.