[swift-evolution] Closure identity (was Re: Compiler directive for current closure reference)

Joe Groff jgroff at apple.com
Tue Feb 23 13:48:24 CST 2016


> On Feb 22, 2016, at 8:00 PM, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> Closures don't have identity (i.e. you can't use === on them). ObjC blocks do, so if you really need it you can pass things around as @convention(objc_block), but closures don't. So I think you'd have to start there if you want to do anything like this.
> 
> Actually, I've wondered about this for a while.
> 
> Closures are pretty weird in that they're a reference type, but they don't have a stable identity. I asked about this once before Swift was open sourced (I think on Twitter), and I believe I was told that it was due to certain optimizations. What are these optimizations, and can we give closures stable identities despite them?

I think it's more proper to think of closures as *immutable*—they're above either reference or value semantics. Closures can capture references to shared mutable state, either by closing over class references or `var` boxes, but that state is shared with code in the same lexical scope, not part of the closure itself. Swift will mess with the underlying identity of closures for a couple of reasons today:

- When functions are passed into contexts with different genericity, we may "reabstract" them, thunking to get a better calling convention for the destination's generic abstraction level. If we weren't allowed to reabstract, function objects would always need to use an inefficient indirect calling convention to be usable from generic contexts.
- We defer closure formation in some cases, particularly when using local `func` decls:

func foo(x: Int, asynchronously: Bool) {
  func bar() { print(x) }

  if !asynchronously {
    bar() // We don't form a closure on this branch
  } else {
    async(bar) // We do on this branch
  }
}

If every reference to `bar` needed to form an equivalent function object, it would be harder to do this.

Future potential optimizations include replacing a function literal that's equivalent with a function or method call with a direct reference to that function (e.g. { $0.method($1, $2) }), or stack-allocating closures to be lazily copied to the heap on escape, the same way ObjC blocks are explicitly heap-promoted with _Block_copy.

Even without these optimization considerations, relying on the identity of specific block objects is fraught with problems, and in practice there's usually a better discriminator available to you somewhere to key on. We could possibly support identity of function or method *declarations*, similar to selectors in ObjC, but those would have to be at best a subtype of all function values.

-Joe

> 
> In a few minutes of research, my guess is that it has to do with thick vs. thin closures: thick closures carry a context object, while thin ones don't. <https://github.com/apple/swift/blob/master/docs/CallingConvention.rst#closures> Is that the issue here? If so…
> 
> - Leaving aside thin closures for the moment, is a thick closure's context object unique enough to be used as its identity? Is function pointer + context unique enough, or does the function pointer change too?
> 
> - Now, for thin closures, obviously we don't want to allocate an entire unnecessary object to carry an empty context around. But what would the costs be if, instead of omitting the context pointer, we replaced it with a unique integer (presumably with the low bit set to avoid colliding with valid context pointers)? Perhaps keep a thread-local counter and combine it with the thread ID so we don't need any locking?
> 
> (As for motivation: besides just being kind of a weird asymmetry, there are certain classes of APIs which are made more convoluted by the lack of closure identity. Basically any API where you can register and unregister multiple handlers with a single object is affected: the "add" operation has to return some kind of token to be used for the "remove" operation, which may now have a different lifetime from the registration itself. It seems simpler to add and remove the closure itself, but the lack of identity prevents that.)
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 
> _______________________________________________
> 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/20160223/26995b3b/attachment.html>


More information about the swift-evolution mailing list