5

I have a function which looks something like this:

func test(closure: () -> ()) {
    let localClosure = { closure() }

    localClosure()
}

This is only an example and does not fully reflect the problem I encountered, obviously here I could have just called closure directly!

It should be clear that in the above code, closure cannot escape. However, I get the error:

Closure use of non-escaping parameter 'closure' may allow it to escape

Now, if localClosure was escaping in some way, I'd understand this error, but it doesn't escape. I even tried annotating localClosure as @noescape (even though that attribute is deprecated in Swift 3), and according to the warning I got:

@noescape is the default and is deprecated

If localClosure is, by default, non-escaping, then why can't another non-escaping closure go inside it? Or is this a bug/limitation of the compiler?

Robert
  • 5,735
  • 3
  • 40
  • 53
  • 1
    Possibly relevant Q&A (at least w.r.t the compiler's bogus warning about `localClosure` being `@noescape` by default, which it isn't): [Why do closures require an explicit `self` when they're all non-escaping by default in Swift 3?](http://stackoverflow.com/questions/39433221/why-do-closures-require-an-explicit-self-when-theyre-all-non-escaping-by-defa) – Hamish Oct 16 '16 at 19:38
  • @Hamish I belive that target thread is actually a perfect dupe mark for this thread (even if it's a slightly different question, the answers to the target also answers this question): the whole discussion (and previous answer) falls apart so a single simple fact once it's clear that non-parameter closures are actually `@escaping` by default. Voting to dupe mark! – dfrib Oct 16 '16 at 21:11
  • @dfri Hmm.... actually thinking about it, I agree – given that the answer is basically "*`localClosure` is actually `@escaping`, and there's currently no (non-deprecated) way of marking it as non-escaping*". – Hamish Oct 16 '16 at 21:41

1 Answers1

5

Non-parameter closures are @escaping, by default

"If localClosure is, by default, non-escaping, then why ..."

Based on the discussion in the comments below (thanks @Hamish), we can state the following facts regarding non-parameter closures in Swift 3.0:

  • They are, contrary to what one might believe, @escaping, by default. As @noescape is deprecated in Swift 3 (see e.g. Xcode 8 release notes or Swift evolution proposal SE-0103), this means that non-parameter closures cannot be made non-escaping without making use of deprecated methods.
  • As described in the following evolution thread, the lack of @noescape attribute for non-parameter closures is a missing feature (somewhat of a regression as this was not a limitation in Swift 2.2), but one that is not necessarily to be implemented in the future (if I'm to understand the answer by Apple dev. Jordan Rose in the linked evolution thread).
  • We may however (still) apply the deprecated @noescape attribute to a non-parameter closure to make it non-escaping, but will then notably be prompted with an incorrect warning (as below, emphasis mine), which has now been reported as a bug by @Hamish, see bug report SR-2969.

    "@noescape is default and is deprecated"

To summarize, localClosure is @escaping, which naturally means it cannot be allowed to wrap the non-escaping closure parameter closure of test(...).

[†] By non-parameter closures, I refer to all closures that are not parameters to a function, i.e., closures that are not supplied to a function as an argument.


As a side-note, which you possibly already knows given your question: we may naturally mark closure as @escaping if we'd wish to process/wrap it as in your example.

func test(closure: @escaping () -> ()) -> () -> () {
    let escapingLocalClosure = { closure() }
    return escapingLocalClosure
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thanks, so basically if I assign a closure to a local variable, the compiler assumes that it *might* outlive the function it's in, effectively making it escaping. It's a shame it can't deduce how it's used, as I think that the original example should be allowed. Maybe in the future! – Robert Oct 16 '16 at 18:00
  • @dfri Why would the compiler not be able to check if `localClosure` can escape? It can detect if a non-escaping closure parameter can escape (if it's captured by an escaping closure, or stored in a variable that outlives the function call), and will emit an error if it does. Consider [this gist](https://gist.github.com/hamishknight/80b2753423e8ee33b562f5ee6d3b4f24) that compiles just fine with the `localClosure` marked as `@noescape` (although it yields a warning about its deprecation). To me, this just looks like a rough edge in the deprecation of `@noescape`, but could well be wrong. – Hamish Oct 16 '16 at 18:53
  • @Hamish Based on your argument it should indeed be able to infer if `localClosure` can escape, but it seems (given this Q&A) that it, currently, cannot. W.r.t. the gist example: try the same with `@escaping` and you will be prompted with the error message _"`@escaping` may only be applied to parameters of function type"_. Error for `@escaping` and deprecation warning prompt for `@noescape`: I would say this is some kind of ambiguity w.r.t. escaping/nonescaping of local scope closures (w.r.t. to their "owning" scopes). I believe also the `@noescape` should yield and error in this use case. – dfrib Oct 16 '16 at 19:05
  • ... or all such closures such as `localClosure` are `@escaping` as per default? Even thought we may not annotate them as such. Could this be considered somewhat buggy behaviour? – dfrib Oct 16 '16 at 19:11
  • @dfri But the compiler can detect if `localClosure` can escape, indeed if you try and let it escape (for example now see [this gist](https://gist.github.com/hamishknight/ae0cb30c4e60771a1c21ef80f8188b9c) :) ) – the compiler will emit an error. To me, this shows that the framework is in place for the compiler to be able to treat local variable functions as `@noescape` (by default they are indeed `@escaping` – try returning it without `@noescape`), but it's currently a rough edge (bug?) with the removal of `@noescape` in favour of `@escaping`. IMO, we should allowed to do this. – Hamish Oct 16 '16 at 19:12
  • @Hamish Thanks for all the feedback; I need pause SO:ing for some hours due to another issue (><), however, so I'll need to come back to this. But **1)** the warning msg prompted for adding (the deprecated!) `@noescape` is, in this case, outright wrong (_"`@noescape` is the default and ..."_) as this is apparently only true for closures that are arguments to functions. **2)** _If_ "local" closures such as `localClosure` are indeed intended to be `@escaping` by default, the isn't the deprec. of `@noescape` premature? E.g. the OP:s example shows just such a use case for the `@noescape`. Thanks! – dfrib Oct 16 '16 at 19:20
  • @dfri Yup, both of your points are spot on. The warning message on the `@noescape` attribute for `localClosure` is completely bogus, as [SE-0103](https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md) only made `@noescape` the default for function closure arguments ([this Q&A](http://stackoverflow.com/questions/39433221/why-do-closures-require-an-explicit-self-when-theyre-all-non-escaping-by-defa) may also be relevant). I also definitely think that the Swift team were premature in deprecating the `@noescape` attribute, as it still has clear use cases. – Hamish Oct 16 '16 at 19:36
  • 1
    What's really interesting is that in Swift 2.3, you couldn't even mark local variable functions as `@noescape`, which means that the Swift team have simultaneously allowed using `@noescape` in this context, and deprecated it :P I'll start writing up a bug report to see what the Swift team make of this. – Hamish Oct 16 '16 at 19:36
  • Filed two bug reports – [one for the bogus warning](https://bugs.swift.org/browse/SR-2969) & one for the regression on [being able to mark local variable closures as `@noescape`](https://bugs.swift.org/browse/SR-2970) :) – Hamish Oct 16 '16 at 20:38
  • @Hamish Nice! I updated my previously quite errorounous answer, thanks for your feedback and discussion :) note that the [evolution thread answer by Jordan Rose](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160822/026711.html) possibly hints that the Swift team is aware about the fact that non-parameter closures may no longer (legally) be non-escaping, but it seems as if they don't really see this as an issue. – dfrib Oct 16 '16 at 21:09
  • @dfri Happy to help :) (I now just need to figure out which, if any, of my comments I should now delete :P). Also thanks for linking to that evolution thread answer, although it's a shame that the Swift team may not consider re-adding `@noescape` to the language – but I guess we'll have to wait and see how the evolution for Swift 4 plays out. – Hamish Oct 16 '16 at 21:36