I quite like the idea of making @noImplicitCopy
unnecessary, and I like the general idea of tagging the binding with that semantic based on how it is spelled. At first I was concerned about more than doubling the set of assignment keywords, but after playing with it I think the concision is a serious advantage.
One lingering concern is how this will impact readability of structs and classes. Hunting for variable declarations isn’t as common as scanning a type for property declarations, and this change would increase the set of watch words to let
, var
, inout
, borrow
, consume
, and presumably const
, eventually.
Some other remaining questions:
-
If I declare
inout x = &y
, isy
still in scope? I would assume not, becausex
now has exclusive access. -
Since the same keywords are still being proposed as operators, would
let x = consume y
andconsume x = y
both mean the same thing? -
Does this pitch also cover parameters? If so, does that mean that
func foo(_ arg: inout T)
now meansarg
is no-implicit-copy within the body offoo
? -
Can property declarations use
borrow
andconsume
? -
I also feel like this doesn’t address @John_McCall’s point about the inconsistency of
&
. I’ll again plug my previous suggestion of making&
mandatory for all borrowings or consumptions of bindings:
Mutability | Local Variable | Local Borrow | Local Consume | Value Parameter | Borrowed Parameter | Consuming Parameter |
---|---|---|---|---|---|---|
Immutable | let x: T |
inout let x: T |
in let x: T |
func f(x: T) (preferred) or func f(x: let T)
|
func f(x: inout let T) |
func f(x: in T) (preferred) or func f(x: in let T)
|
Mutable | var x: T |
inout x: T (preferred) or inout var x: T
|
in var x: T |
func f(x: var T) |
func f(x: inout T) (preferred) or func f(x: inout var T)
|
func f(x: in var T) (mutability is not visible to caller) |
Assignment | Rule |
---|---|
let x = y |
Implicit immutable copy of y . |
var x = y |
Implicit mutable copy of y . |
inout let x = &y or in x = &y
|
Immutable borrow/consume of y . y can be mutable or immutable. |
inout x = &y or in var x = &y
|
Mutable borrow /consume of y . y must be mutable. |
inout let x: T var y = copy(x)
|
Values must be explicitly copied out of in let , in var , inout let , or inout var . Mutability of copy is independent. |
var x: T func f(_: inout let T) { } f(&x)
|
All bindings can be passed to inout let parameters. (Callee immutably borrows the binding for the duration of the call.) |
inout var x: T func f(_: inout x: T) { } f(&x)
|
var , in var , and inout var bindings can be passed to inout var parameters. (Existing Swift semantics.) |
let x: T func f(_: in T) { } f(&x)
|
let , var , in let , and in var bindings can be passed to in parameters. Consumes the caller’s binding; callee loses mutability. (Can’t pass inout because access is transferred to the callee, but the callee wouldn’t transfer ownership back.) |
let x: T func f(_: in var T) { } f(&x)
|
let , var , in let , and in var bindings can be passed to in var parameters. Caller doesn’t see any difference from in parameters, but value is mutable within callee. |
// To end the lifetime of a binding early, assign the binding to _.
// Ending the lifetime of an inout binding writes its value back.
func addFortyTwo(to x: inout Int) {
x += 42
_ = &x
print("Added!")
}
// To end the lifetime of a binding while passing it as a parameter, use the standard library’s drop() helper function.
// (note this won’t work with inout bindings; drop those explicitly with assignment to _):
func drop<T>(_ binding: in T) -> in T {
return &binding
}
var heavyWeight = MyStruct()
doStuff(with: drop(heavyWeight))
// To read the value of a binding, use the standard library’s copy() helper function.
@_semantics("binding.copy")
func copy<T>(_ binding: inout let T) -> T {
// Unused implementation; the compiler actually never emits a call to this function.
// It just exists to make the type system happy.
return _Builtin.copy(&binding)
}
inout var x: T
var copyOfX = copy(&x)