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

Andrew Bennett cacoyi at gmail.com
Tue Dec 8 16:57:56 CST 2015


Thanks everyone for joining the discussion, I'm glad that other people are
thinking about this too. I should have mentioned up-front, in case it was
unclear, I think this needs more discussion before it's ready as a formal
proposal.

I think most people here seem to agree that blocks capturing objects can be
a problem, and we don't want to have to type a bunch of new things to deal
with it. What we're unsure of is how to deal with it.

The only thing I explicitly didn't agree with was that { self!.foo } is a
problem because of self being weak. I think the problem here is the use of
force-unwrap.

Also, I think that the discussion about when properties are immutable, and
not necessarily being able to configure an immutable structure is a good
one. It is also perhaps off-topic, except where it affects the scope of
impact of this proposal. Do we agree that it's sufficient for this proposal
to say that some properties could be captured as value types, and as the
swift compiler gets smarter those cases will grow?

In Ilya's example:
if let computed = c?.compute() {
    print(computed)
}
I agree that this will change what the code does, but I think a programmer
using dispatch_async will be familiar with if-statements, and so should be
able to determine the implications. I think that in this case the IDE would
suggest two solutions "add ? to c.compute()" and "strongly capture c <a
warning about retain cycles>". I think the important thing would be that
the "default" case is that you don't have memory issues, you can't compile
the code if you haven't acknowledged that with '?' or '[strong c]'.

Kevin, I think you should definitely propose that (weak in outer and inner
closure), it's one of the surprises that led me to this proposal. I also
really like your suggesting of requiring explicit weak/strong on self, I
think this could solve many cases. What I'm not sure of is whether this
should be restricted to self, or the "owners" of the block calling method.
ie.

func doWork(something: Something) {
     something.calculateSomeStuff { (a,b) in
          return self.calculate(a,b)
     }
}

In this case I think that strong/weak should be made explicit on
'something' not on 'self'.

I think that the main points at the moment are:
 1. The impact of requiring explicit strong/weak on self/something.
 2. How often requiring 'strong' on *non-trivial* captures would happen.
 3. If you can mitigate most cases with small changes to APIs.

An example of an api change would be:

let data = 123
worker.doWork { [strong worker] (item: Item) in
    worker.work(data, withItem: item)
}

becomes:

let data = 123
worker.doWork { (worker: Worker, item: Item) in
    worker.work(data, withItem: item)
}


On Wed, Dec 9, 2015 at 6:28 AM, Kevin Ballard via swift-evolution <
swift-evolution at swift.org> wrote:

> 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
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151209/37ff85f7/attachment.html>


More information about the swift-evolution mailing list