[swift-users] Compiler refuses non-escaping closure calls in nested function: Bug?

Zhao Xin owenzx at gmail.com
Mon Oct 10 04:30:54 CDT 2016


> It is just like there were two ways to do the job, you thought it worked
in this way, but it chose the other way. Both ways lead to the same result.

I think it is because the compiler **flatten** your inner function instead
of calculated the result.

The variable scope of `closure` is outside the `func closureDoubled`, so it
is `escaping` from that function. However, since `closure` is defined
outside, there is no place in `func closureDoubled` mark as `@escaping`, so
the compiler asked me to add `@escaping` in the place where it is firstly
defined.

Zhaoxin



On Mon, Oct 10, 2016 at 4:06 PM, Jean-Denis Muys <jdmuys at gmail.com> wrote:

> It seems to me you are shooting while blindfolded.
>
> I changed you code `let result = closure(n)` to `let result = closure(1)`.
> I thought that should eliminate the error. It didn't. The compiler asked me
> to change the whole code as below:
>
>
> Why did you expect changing which argument to send the closure to make any
> difference?
>
> func mainFunction(closure: @escaping (Int) -> Int) -> Int {
>
>
> Sure declaring the closure as escaping shuts up the compiler. At the cost
> of having an escaping closure in a context where the closure is guaranteed
> no to escape. My understanding of the distinction between escaping and non
> escaping is it enables compiler optimisations (in the non escaping case)
> that would not be possible in the general case. Therefore this
> compiler-mandated change kills performance for nothing
>
>
> So I think in `func closureDoubled(_ n: Int) -> Int`, the `closure` is
> escaping other than non-escaping.
>
>
> No. The closure is not escaping. At least as far as I can see. Can you
> point out where it is escaping? Declaring a closure as escaping does not
> make it so. It only *allows* it to escape.
>
> Note that here, “escaping” means escaping the scope where it is declared,
> i.e. the `mainFunction` function. Clearly, it does not.
>
>
>  The result is not calculated inside the function `closureDoubled`, it
> was calculated at `return temp1+temp2`, that is what I think can explain
> the behavior.
>
>
> Where does this speculation comes from? It totally contradicts the story
> that the source code tells. temp1 and temp2 and Int variables that are
> local to `mainFunction `, while `result` is local to the nested function
> `closureDoubled`. Unless you use of the word “result” does not refer to the
> `result` variable. I apologise for the poor choice of variable names that
> made possible this confusion.
>
> I don't know why at first. It just like there were two ways to do the job,
> you thought it worked in this way, but it chose the other way. Both ways
> lead to the same result.
>
>
> You lost me in that sentence. What do the pronouns refer to?
>
> Then I changed your code to
>
>
> yes you totally changed my code by adding a closure parameter to the
> nested function, in which you passed the closure that was passed to the
> outer function.
>
> By doing so, however, you altered significantly my use case. This is
> because the point of having a nested function, as opposed to a separate
> function defined outside the outer function, is for the nested function to
> capture the environment of its containing function.
>
> Of course, this begs the question: this is semantically equivalent, so why
> objecting to that? There are two answers to that question:
>
> 1- preventing a nested function from capturing a local closure is a
> significant limitation which essentially demotes closure from the status of
> first-class citizen. The artificial limitation of expressiveness in the
> language is unwarranted and a serious flaw (if intended. I still believe
> this is a bug)
> 2- A very common pattern for nested functions is to make recursive
> algorithms more memory-efficient by forwarding the recursion to a nested
> function whose parameters are only those values that change during the
> recursion. Other values, such as the parameter you just added in your
> change, if passed as parameters, will only waste stack space since they
> will never change in the recursion. This will also degrade performance
> slightly because the code will have to push this unchanging value to the
> stack every time a recursive call is made.
>
> Note that this was the context where I was bitten by the problem. Of
> course, you could expect a smart compiler to recognise that is doesn’t have
> to push unchanging parameter values onto the stack in recursive calls.
> Similarly, there are smart compiler which recognise terminal recursion and
> transform recursion into iteration. I even encountered in the past (Lisp)
> compilers which automatically transformed simple cases of non-terminal
> recursion into terminal-recursive versions (by adding an accumulator
> parameter), followed by transformation into iterative code. I do not expect
> typical compilers to do that (where “typical” is left undefined, but would
> include GCC, Clang, Swift). This is why I tend to implement my recursive
> calls in the way just described.
>
> Everything works as expected now.
>
>
> Expected, perhaps. As intended, certainly not.
>
> So I think the reason is just because of `closure` was not define in `func
> closureDoubled` in the first code snippet. It was defined outside, so it
> was escaping. What do you think?
>
>
> I think this explanation does not make sense to me. Maybe I am missing
> something. Could you possibly detail what you mean?
>
> Thank you very much for your attempts. It’s possible something is escaping
> me (pun intended). But I still believe this is a bug in the compiler, if
> not in the language.
>
> Best regards.
>
> Jean-Denis
>
>
>
>
> On 10 Oct 2016, at 02:45, Zhao Xin <owenzx at gmail.com> wrote:
>
> I changed you code `let result = closure(n)` to `let result = closure(1)`.
> I thought that should eliminate the error. It didn't. The compiler asked me
> to change the whole code as below:
>
> func mainFunction(closure: @escaping (Int) -> Int) -> Int {
>
>
>     func closureDoubled(_ n: Int) -> Int {
>         let result = closure(1)
>         return 2*result
>     }
>
>
>     let temp1 = closure(1)
>     let temp2 = closureDoubled(1)
>     return temp1+temp2
> }
>
> So I think in `func closureDoubled(_ n: Int) -> Int`, the `closure` is
> escaping other than non-escaping. The result is not calculated inside the
> function `closureDoubled`, it was calculated at `return temp1+temp2`,
> that is what I think can explain the behavior.
>
> I don't know why at first. It just like there were two ways to do the job,
> you thought it worked in this way, but it chose the other way. Both ways
> lead to the same result.
>
> Then I changed your code to
>
> func mainFunction2(closure:  (Int) -> Int) -> Int {
>
>
>     func closureDoubled(_ n: Int, closure2:(Int) -> Int) -> Int {
>         let result = closure2(1)
>         return 2*result
>     }
>
>
>     let temp1 = closure(1)
>     let temp2 = closureDoubled(1, closure2: closure)
>     return temp1+temp2
>
> }
>
> Everything works as expected now. So I think the reason is just because of
> `closure` was not define in `func closureDoubled` in the first
> code snippet. It was defined outside, so it was escaping. What do you think?
>
> Zhaoxin
>
> On Mon, Oct 10, 2016 at 8:07 AM, Jean-Denis Muys via swift-users <
> swift-users at swift.org> wrote:
>
>> Here is a contrived reduction of my problem
>>
>> func mainFunction(closure:(Int) -> Int) -> Int {
>>
>>     func closureDoubled(_ n: Int) -> Int {
>>         let result = closure(n)
>>         return 2*result
>>     }
>>
>>     let temp1 = closure(1)
>>     let temp2 = closureDoubled(1)
>>     return temp1+temp2
>> }
>>
>> The line "let result = closure(n)" is refused by the compiler with the
>> error message "declaration over non closing parameter may allow it to
>> escape".
>>
>> First off, while I know what an escaping or a non-escaping closure is, I
>> find this error message utterly impossible to understand. To begin with,
>> the sentence "non closing parameter" is meaningless to me.
>>
>> In any case, my main function is passed a non-escaping closure. I want to
>> call it from inside it, the compiler is ok with. I want also to call it
>> from a nested function, but the compiler disagrees.
>>
>> I believe the compiler should not complain here. Did I miss anything?
>>
>> Jean-Denis
>>
>>
>>
>>
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
>>
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20161010/fdf06025/attachment.html>


More information about the swift-users mailing list