[swift-evolution] Proposal: Closures capture weak by default

Kevin Ballard kevin at sb.org
Tue Dec 8 13:28:17 CST 2015

On Tue, Dec 8, 2015, at 07:55 AM, Joe Groff via swift-evolution wrote:
> One reason cycles are so prevalent with closures is that it's easy to capture too much, since referencing 'self.foo' always captures the entirety of 'self'. If the properties of 'self' you need are immutable, or you don't need to see their mutations, you can reduce the risk of cycles by capturing those properties explicitly, turning this:
>> self.setCallback { doStuffWith(self.zim, self.zang) }
> into:
>> self.setCallback {[zim, zang] in doStuffWith(zim, zang) }
> For 'let' properties of classes, it'd be reasonable to propose having
> closures capture the *property* directly by default in this way
> instead of capturing 'self' (and possibly allowing referencing them
> without 'self.', since 'self' wouldn't be involved in any cycle formed
> this way).

Interesting idea, but I'm concerned that this would be surprising and
open up a different class of bug, where properties of self
unexpectedly outlive self even though they were never referenced
without going through self. This can be a problem if self has a
deinit that does relevant work, or if the values of the properties
themselves depend on self somehow. As a trivial example, a property
of self might contain an unowned reference back to self, because it
knows it will never outlive self, at least until this rule is
introduced and causes a dispatch_async() to access the unowned
reference after self has deinited.

Personally, I'm happy with just using an explicit capture list for my
properties if I don't want to capture self. Using this along with the
practice of always omitting the `self.` whenever possible means the
compiler yells at me if I accidentally capture self in an escaping
closure (e.g. when I try and use a method/property without the `self.`),
forcing me to make a decision there about whether I want to capture self
or add a capture list for the property.

Another alternative is to require an explicit capture list to capture
self in an escaping closure, so you have to say `[self]` or `[weak
self]`. This way you're forced to make a decision always. Potential
downsides I can think of:

1. It's verbose. Application code often has a lot of escaping closures,
   and most of them probably don't have capture lists, so that's a lot
   of capture lists to add.
2. It's still possible to accidentally capture self without realizing it
   by use of a local nested function that is itself captured. Of course,
   this is the same scenario that lets you capture self without even
   writing `self.` since local nested functions allow you to omit the
   `self.`, so it's already a known issue.
3. How does this interact with captures of self from enclosing closures?
   Right now, if I have `{ [weak self] in foo({ /* self is still weak
   here */ }) }`; does the inner closure need a capture list or not? How
   about if the outer capture is strong? `{ [self] in foo({ /* self is
   strong here */ }) }`? Of course, this rule would fix the current
   hazard of weakly capturing self at the wrong level, e.g. `{ foo({
   [weak self] in ... }) }` where self is strongly captured in the outer
   closure, which seems to surprise people. Although now that I think of
   it, I'll probably submit a proposal to change that, so the outer
   closure captures self weakly if it's only capturing it at all because
   of a weak capture in a nested closure.

-Kevin Ballard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151208/9a1d0eb5/attachment.html>

More information about the swift-evolution mailing list