[swift-evolution] Proposal: Allow `[strong self]` capture in closures and remove the `self` requirement therein

Drew Crawford drew at sealedabstract.com
Tue Dec 15 19:12:51 CST 2015


> Can you explain what is so evil about func evil() when it is called from an asynchronously-executed closure?


Apologies for earlier brevity, perhaps it would be helpful for me to present a more complete, realistic example.  Paste the following into main.swift:

import Foundation

print("Hello, World!")

typealias evilArg = [String:String]
var strongReference: evilArg! = nil
func evil(foo:evilArg ) {
    strongReference = foo
}

final class Photo {
    var data = [UInt8](count: 100000000, repeatedValue: 0) //a large amount of data
    let metadata: [String: String] = [:] //a small amount of data
    func save() {
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
            evil(self.metadata)
        }
    }
}
let p = Photo()
p.save()
//leaks Photo, data, and metadata

let sema = dispatch_semaphore_create(0)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)

In this example, the memory usage at the end of the program is 100MB:



This is because Photo, data, and metadata are all leaked by the evil function.

This is the (surprising?) behavior specified in the Swift Book:

> [A capture occurs when] the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.


Even though evil may seem (to the casual programmer who does not read language specifications for funsies) like the closure captures only the evil argument `metadata`, it *actually* captures Photo (and therefore data)

The capture of Photo (data) is somewhat clear when we write

evil(self.metadata)

But it is hidden when we write

evil(metadata)

As you propose.

I think that if we are going to have semantics that capture Photo (data), it had better look like it in a cursory inspection.  The existing syntax is not as great as it could be, but it provides a clue.  I think even that the existing syntax isn't good enough, because I expect that many people are unaware of this particular dark corner.  As you say:

> Can you explain what is so evil about func evil() when it is called from an asynchronously-executed closure?  I don't see an obvious bug here.

It may very well be that there is no *obvious* bug, and so what we may actually need is a proposal to make the programmer even more explicit when referencing self, not less.

But in any case.  For `strong self` to make sense, we would need to do one of two things:

1.  We could change Swift semantics to only capture metadata in this example, however this is a breaking change, and a very very nontrivial one.  I do not know why it is specified this way in the first place, perhaps a designer can weigh in on that.  I do know that ObjC is very similar, so there may be compatibility implications.

2.  We could introduce additional syntax to provide compiler diagnostics to guard in this case, e.g.

func lessEvil(@noescape foo:evilArg ) { //@noescape currently not supported for non-closure parameters
     self.strongReference = foo //should generate diagnostic about escaping a @noescape parameter
}

and then

dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { [strong self] in
            evil(metadata) //should generate diagnostic that self is required for functions without @noescape
            lessEvil(metadata) //no diagnostic since parameter is @noescape
}

I think both of these fixes to the proposal are fairly impractical, but they would weaken my vote to -0.5.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151215/ef1f91f8/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screen Shot 2015-12-15 at 6.26.52 PM.jpg
Type: image/jpeg
Size: 23371 bytes
Desc: not available
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151215/ef1f91f8/attachment.jpg>


More information about the swift-evolution mailing list