after having a discussion with a coworker about an interesting idea he had, I came to realize that a weird case can be constructed with autoclosure and escaping.
Let me first post some code and discuss it afterwards. This is straight from a Xcode 10.2 playground:
The method printExpression() shows the normal use of autoclosure, so nothing special to see here.
The AutoEncloser class takes an expression and holds on to it as a closure. That's where the problems start.
The caller (AutoEncloser(expression: "escaping: (str)")) has no idea that he might be in for a retain cycle.
The caller also has no idea that the behaviour of the expression can change after the function call, where it has been supplied to, has returned. If you run the code you can see that in the two last lines.
The obvious fix is to forbid the usage of an escaping autoclosure. IMHO the Swift compiler should at least create a warning for that, as it seems to be an obvious anti-pattern to me.
You've explicitly tagged it as @escaping, signalling intent, and there's already an error if you don't do that. How would you silence a warning if you did intend it to escape? This might be ill advised in many situations, but I don't think it's much more harmful than a regular escaping closure.
There is no hint that evil side-effects lurk there. But as the example shows they clearly do. I consider that very bad.
I would think the @autoclosure was created under the assumption, that calculating the expression not before the call to the function is generally safe when you do it in the scope of the called function. If I did not miss something this assumption should always hold.
But said assumption breaks spectacularly, when you hold on to the closure and run it some time later potentially even on a different thread. I would argue that was probably not an intended or considered use-case of @autoclosure when it was introduced.
Nimble explicitly relies on this behavior with its expect() construct:
expect(foo).toEventually(equal(bar))
The expect(…) creates an expectation, which a test like toEventually can check repeatedly, as many times as it likes, after the call to expect is completed.
It may be surprising that a callee can reevaluate a naked expression after the call is complete, but this behavior is by design as Xiaodi notes, and has existing use cases.
This is one of many things in Swift where I wish our dev environments had more elegant, not-too-intrusive visualizations of static analysis right in the editor: subtle visual indications at the point of use of which expressions are autoclosured, which closures are escaping, which identifiers inside a closure capture something, when . is doing a dynamic dispatch, which assignments happen with value semantics, etc.
On the other hand, I'd argue that if you have to know at the call site that an expression is being autoclosured, the designer of the function you're calling has already failed. expect(foo).toEventually { equal(bar) } would have been fine.
(I'm minorly in support of this change, but I'm not sure it's worth the massive source breakage from the camps that do use escaping autoclosures. It's not like we'd be able to remove the feature or even warn about it unconditionally, because that would break existing libraries.)
[EDIT: Paul points out I made a mistake in my code example, but the point stands.]
So if I get you right, this is mainly used to get rid of extra {} while at the same time sacrificing readability and safety.
And if you consider how easy it would be write a migration for the change, I see no good reason to keep the escaping autoclosure behaviour. Swift is being advertised as having safety as a design goal after all.
I am not a fan of this pitch. I have a couple parser DSLs where I almost exclusively use @autoclosure with @escaping The whole point of the APIs are that things are escaping, so they are clear.