Increase availability of implicit self
in @escaping
closures when reference cycles are unlikely to occur
- Proposal: SE-0269
- Author: Frederick Kellison-Linn
- Review Manager: Ted Kremenek
- Status: Implemented (Swift 5.3)
- Implementation: apple/swift#23934
- Bug: SR-10218
- Forum threads: Discussion, Pitch, Review thread
Modify the rule that all uses of self
in escaping closures must be explicit by allowing for implicit uses of self
in situations where the user has already made their intent explicit, or where strong reference cycles are otherwise unlikely to occur. There are two situations covered by this proposal. The first is when the user has explicitly captured self
in the closure's capture list, so that the following would compile without error:
class Test {
var x = 0
func execute(_ work: @escaping () -> Void) {
work()
}
func method() {
execute { [self] in
x += 1
}
}
}
Secondly, this proposal would make implicit self
available in escaping closures when self
is a value type, so that the following would become valid:
struct Test {
var x = 0
func execute(_ work: @escaping () -> Void) {
work()
}
func method() {
execute {
x += 1
}
}
}
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()
}
It also results in a lot of unnecessary repetition. The motivation for requiring explicit usage of self
is to force the user to make the intent to capture self
explicit, but that goal is accomplished after the first explicit usage of self
and is not furthered by any of the subsequent usages.
In codebases that make heavy use of asynchronous code, such as clients of PromiseKit, or even Apple's new Combine and SwiftUI libraries, maintainers must either choose to adopt style guides which require the use of explicit self
in all cases, or else resign to the reality that explicit usage of self
will appear inconsistently throughout the code. Given that Apple's preferred/recommended style is to omit self
where possible (as evidenced by examples throughout The Swift Programming Language and the SwiftUI Tutorials), having any asynchronous code littered with self.
is a suboptimal state of affairs.
The error is also overly conservative—it will prevent the usage of implicit self
even when it is very unlikely that the capture of self
will somehow cause a reference cycle. With function builders in SwiftUI, the automatic resolution to the "make capture semantics explicit" error (and indeed, the fix-it that is currently supplied to the programmer) is just to slap a self.
on wherever the compiler tells you to. While this likely fine when self
is a value type, building this habit could cause users to ignore the error and apply the fix-it in cases where it is not in fact appropriate.
There are also cases that are just as (if not more) likely to cause reference cycles that the tool currently misses. Once such instance is discussed in Future Directions below. These false negatives are not addressed in this proposal, but improving the precision of this diagnostic will make the tool more powerful and less likely to be dismissed if (or when) new diagnostic cases are introduced.
First, 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. 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.
The compiler would also offer an additional fix-it when implicit self
is used:
execute { // <- Fix-it: capture 'self' explicitly to enable implicit 'self' in this closure. Fix-it: insert '[self] in'
let foo = doFirstThing()
performWork(with: bar)
doSecondThing(with: foo)
cleanup()
}
Second, if self
is a value type, we will not require any explicit usage of self
(at the call/use site or in the capture list), so that if self
were a struct
or enum
then the above could be written as simply:
execute {
let foo = doFirstThing()
performWork(with: bar)
doSecondThing(with: foo)
cleanup()
}
Whenever self
is declared explicitly in an escaping closure's capture list, or its type is a value type, any code inside that closure can use names which resolve to members of the enclosing type, without specifying self.
explicitly. In nested closures, the innermost escaping closure must capture self
, so the following code would be invalid:
execute { [self] in
execute { // Fix-it: capture 'self' explicitly to enable implicit 'self' in this closure.
x += 1 // Error: reference to property 'x' in closure requires explicit use of 'self' to make capture semantics explicit. Fix-it: reference 'self.' explicitly.
}
}
This new behavior will also be available for closures with unowned(safe)
and unowned(unsafe)
captures of self
since the programmer has clearly declared their intent to capture self
(as for strong captures). For weak
captures of self
, it's not immediately clear what bare reference to an instance property even means when the local self
is bound weakly—should it be treated as though the programmer had written self?.
? Should there be a special syntax available in this context, such as ?.x
? Should we offer a diagnostic offering to insert self?.
? Or should we instead suggest that the programmer re-bind self
strongly via a guard
statement? There are enough open questions here that have not been sufficiently discussed, so this proposal leaves the handling of weak self
captures as a future direction worthy of further consideration.
The existing errors and fix-its have their language updated accordingly to indicate that there are now multiple ways to resolve the error. In addition to the changes noted above, we will also have:
Error: call to method <method name> in closure requires explicit use of 'self' to make capture semantics explicit.
The new fix-it for explicitly capturing self
will take slightly different forms 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). Since empty capture lists are allowed ({ [] in ... }
), the fix-it will cover this case too.
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]
. In the unusual event that the user has captured another variable with the name self
(e.g. [self = "hello"]
), we will offer a note that this does not enable use of implicit self
(in addition to the existing error attached to the attempted use of implicit self
):
Note: variable other than 'self' captured here under the name 'self' does not enable implicit 'self'.
If the user has a capture of weak self
already, we offer a special diagnostic note indicating that weak
captures of self
do not enable implicit self
:
Note: weak capture of 'self' here does not enable implicit 'self'.
If either of the two above notes are present, we will not offer the usual fix-its for resolving this error, since the code inserted would be erroneous.
This proposal makes previously illegal syntax legal, and has no effect on source compatibility.
This is an entirely frontend change and has no effect on ABI stability.
This is not an API-level change, and has no effect on API resilience.
While this proposal opens up implicit self
in situations where we can be reasonably sure that we will not cause a reference cycle, there are other cases where implicit self
is currently allowed that we may want to disallow in the future. One of these is allowing bound method references to be passed into escaping contexts without making it clear that such a reference captures self
. For example:
class Test {
var x = 0
func execute(_ work: @escaping () -> Void) {
work()
}
func method() {
execute(inc) // self is captured, but no error!
execute { inc() } // error
}
func inc() {
x += 1
}
}
This would help reduce the false negatives of the "make capture semantics explicit" error, making it more useful and hopefully catching reference cycles that the programmer was previously unaware of.
It might be desirable to add improved diagnostic information (or similarly do away with the error) in the case where the programmer has explicitly captured self
, but only as a weak reference. For the reasons noted in this proposal, this was not pursued, but answering the lingering questions and issuing a follow-up proposal would fit nicely with this direction.
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.
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.