Introduction
Discussion thread: Review capture semantics of self
Bug: SR-10218
Modify the rule that all uses of self
in escaping closures must be explicit by allowing for implicit uses of self
as long as self
is explicitly declared in the capture list for the closure. This would allow the following to compile without error:
class Test {
var x = 0
func execute(_ work: @escaping () -> Void) {
work()
}
func method() {
execute { [self] in
x += 1
}
}
}
Motivation
In order to prevent users from inadvertently creating retain cycles, the Swift compiler today requires all uses of self
in escaping closures to be explicit. Attempting to reference a member x
of self
without the self
keyword gives the error:
error: reference to property 'x' in closure requires explicit 'self.' to make capture semantics explicit
In codebases that choose to omit self
where possible, this can result in a lot of unwanted noise, if many properties of self
are used in a row:
execute {
let foo = self.doFirstThing()
performWork(with: self.bar)
self.doSecondThing(with: foo)
self.cleanup()
}
Proposed solution
Allow the use of implicit self
when it appears in the closure's capture list. The above code could then be written as:
execute { [self] in
let foo = doFirstThing()
performWork(with: bar)
doSecondThing(with: foo)
cleanup()
}
This change still forces code which captures self
to be explicit about its intentions, but reduces the cost of that explicitness to a single declaration.
EDIT: With this change explicit capture of self
would be one of two ways to get rid of the error, with the current method of adding self.
to each property/method access (without adding self
to the capture list) remaining as a viable option. This change makes previously invalid code valid, and is entirely source compatible.
The compiler would also offer an additional fix-it when implicit self
is used:
execute { // <- Fix it: insert '[self] in ' to capture `self` explicitly
let foo = doFirstThing()
performWork(with: bar)
doSecondThing(with: foo)
cleanup()
}
This will require different versions depending on whether there is a capture list already present ("insert 'self,
'"), whether there are explicit parameters ("insert '[self]
'"), and whether the user the user has already captured a variable with the name self
(in which case the fix-it would not be offered). If anyone can think of other cases where the fix-it would need to be tweaked, please note that in the discussion below!
This new rule would only apply when self
is captured directly, and with the name self
. This includes captures of the form [self = self]
but would still not permit implicit self
if the capture were [y = self]
or if you did something like:
func method() {
let y = self
execute { [self = y]
x += 1 // error
}
}
Alternatives considered
Always require self
in the capture list
The rule requiring the use of explicit self
is helpful when the code base does not already require self
to be used on all instance accesses. However, many code bases have linters or style guides that require self
to be used explicitly always, making the capture semantics opaque. Always requiring self
to be captured in the capture list explicitly would ensure that there are no self
captures that the programmer is unaware of, even if they naturally use self
for instance accesses. This would be a more drastic, source breaking change (and is not ruled out by adopting this change), so it was not seriously pursued as part of this proposal.
Eliminate the former fix-it
A less extreme solution to the problem described above is to simply stop offering the current fix-it that suggests adding the explicit self.
at the point of reference in favor of only recommending the explicit capture list fix-it, when possible.