Compiler refuses non-escaping closure calls in nested function


(Jean-Denis Muys) #1

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


(Zhao Xin) #2

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Joe Groff) #3

I agrre that the compiler should be able to determine that the closure is nonescaping in this case, and that that diagnostic is in dire need of revision. Would you be able to file a couple of bug reports, one to allow this example, and one to improve the error message?

-Joe

···

On Oct 9, 2016, at 5:07 PM, Jean-Denis Muys via swift-users <swift-users@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?


(Ole Begemann) #4

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.

The error message I'm seeing in Xcode 8.0 is "Declaration closing over *non-escaping* parameter 'closure' may allow it to escape", so I don't know where you're seeing the "non closing parameter". And "non-escaping parameter" does make a lot more sense, I think.

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?

I think the error message is actually quite good, given that the compiler apparently is taking some shortcuts to prove that a parameter doesn't escape.

By declaring a function that closes over the non-escaping parameter, you're creating a situation that *may* allow the non-escaping closure to escape, i.e. the compiler can't guarantee anymore that it won't escape. For example, you could do assign the `closureDoubled` function to a variable that's outside the scope of `mainFunction`:

     // Variable outside the scope of mainFunction
     var f: (Int) -> (Int) = { $0 }

     func mainFunction(closure: (Int) -> Int) -> Int {

         func closureDoubled(_ n: Int) -> Int {
             let result = closure(n)
             return 2*result
         }

         // closure would escape here
         f = closureDoubled
  ...
     }

     mainFunction { $0 }
     f(5)

If this were allowed, `closure` would be able to escape. I think this possibility explains the error message.

Now the compiler *could* of course check for this and I think you're right in arguing that it *should* ideally perform more sophisticated checks, but since it currently seems to be taking some shortcuts in guaranteeing that a parameter doesn't escape, it has to disallow anything it can't verify as correct.

Ole


(Jean-Denis Muys) #5

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@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@swift.org <mailto:swift-users@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@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jean-Denis Muys) #6

Thanks Ole,

You are perfectly right regarding the error message. For some reason, I misread it. I am sorry about that.

The actual error message, as you point out, makes much more sense!

The rest of your explanation makes sense too, except:

When I declare my closure as @noescape, (the default), I explicitly tell the compiler that, despite the fact closing over it *may* allow it to escape, I, the programmer, guarantee that it will not.

I would understand a warning, but I don’t understand that the compiler insists on putting out an error.

So, while the compiler could perhaps, as you say, perform more sophisticated checks, I don’t even request that. I request however, to be able to tell the compiler that I know better.

If I am wrong, and if I still let the closure escape despite my promise, then I made a programming error, and my code can legitimately crash.

This is no different than using the “!” to promise the compiler that an optional is not nil: if I don’t keep my promise, the program crashes.

Last, in presence of a similar warning (instead of an error), a simple way to squelch the warning would be to make the @noescape declaration explicit.
This would require un-deprecating it.

I now feel that I should propose that as an evolution.

What do you think?

Jean-Denis

···

On 10 Oct 2016, at 12:52, Ole Begemann <ole@oleb.net> wrote:

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.

The error message I'm seeing in Xcode 8.0 is "Declaration closing over *non-escaping* parameter 'closure' may allow it to escape", so I don't know where you're seeing the "non closing parameter". And "non-escaping parameter" does make a lot more sense, I think.

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?

I think the error message is actually quite good, given that the compiler apparently is taking some shortcuts to prove that a parameter doesn't escape.

By declaring a function that closes over the non-escaping parameter, you're creating a situation that *may* allow the non-escaping closure to escape, i.e. the compiler can't guarantee anymore that it won't escape. For example, you could do assign the `closureDoubled` function to a variable that's outside the scope of `mainFunction`:

   // Variable outside the scope of mainFunction
   var f: (Int) -> (Int) = { $0 }

   func mainFunction(closure: (Int) -> Int) -> Int {

       func closureDoubled(_ n: Int) -> Int {
           let result = closure(n)
           return 2*result
       }

       // closure would escape here
       f = closureDoubled
  ...
   }

   mainFunction { $0 }
   f(5)

If this were allowed, `closure` would be able to escape. I think this possibility explains the error message.

Now the compiler *could* of course check for this and I think you're right in arguing that it *should* ideally perform more sophisticated checks, but since it currently seems to be taking some shortcuts in guaranteeing that a parameter doesn't escape, it has to disallow anything it can't verify as correct.

Ole


(Zhao Xin) #7

Thank you, Ole. Your reply teaches me a lot.

Zhaoxin

···

On Mon, Oct 10, 2016 at 6:52 PM, Ole Begemann via swift-users < swift-users@swift.org> wrote:

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.

The error message I'm seeing in Xcode 8.0 is "Declaration closing over
*non-escaping* parameter 'closure' may allow it to escape", so I don't know
where you're seeing the "non closing parameter". And "non-escaping
parameter" does make a lot more sense, I think.

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?

I think the error message is actually quite good, given that the compiler
apparently is taking some shortcuts to prove that a parameter doesn't
escape.

By declaring a function that closes over the non-escaping parameter,
you're creating a situation that *may* allow the non-escaping closure to
escape, i.e. the compiler can't guarantee anymore that it won't escape. For
example, you could do assign the `closureDoubled` function to a variable
that's outside the scope of `mainFunction`:

    // Variable outside the scope of mainFunction
    var f: (Int) -> (Int) = { $0 }

    func mainFunction(closure: (Int) -> Int) -> Int {

        func closureDoubled(_ n: Int) -> Int {
            let result = closure(n)
            return 2*result
        }

        // closure would escape here
        f = closureDoubled
        ...
    }

    mainFunction { $0 }
    f(5)

If this were allowed, `closure` would be able to escape. I think this
possibility explains the error message.

Now the compiler *could* of course check for this and I think you're right
in arguing that it *should* ideally perform more sophisticated checks, but
since it currently seems to be taking some shortcuts in guaranteeing that a
parameter doesn't escape, it has to disallow anything it can't verify as
correct.

Ole

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Zhao Xin) #8

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@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@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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Ole Begemann) #9

When I declare my closure as @noescape, (the default), I explicitly tell
the compiler that, despite the fact closing over it *may* allow it to
escape, I, the programmer, guarantee that it will not.

I would understand a warning, but I don’t understand that the compiler
insists on putting out an error.

So, while the compiler could perhaps, as you say, perform more
sophisticated checks, I don’t even request that. I request however, to
be able to tell the compiler that I know better.

If I am wrong, and if I still let the closure escape despite my promise,
then I made a programming error, and my code can legitimately crash.

I wouldn't like this. I much prefer the current state where this is an error. That said, it would indeed be great if the compiler were smarter when it does the analysis whether a closure actually escapes.

There is already a bug report for this, by the way: SR-2274 [1]

[1]: https://bugs.swift.org/browse/SR-2274

This is no different than using the “!” to promise the compiler that an
optional is not nil: if I don’t keep my promise, the program crashes.

Last, in presence of a similar warning (instead of an error), a simple
way to squelch the warning would be to make the @noescape declaration
explicit.
This would require un-deprecating it.

I now feel that I should propose that as an evolution.

What do you think?

Again, I don't think we should go that route. I prefer the status quo.


(Jean-Denis Muys) #10

So I suppose you feel this case is different from the (unsafe) implicitly unwrapping of optionals with the “!” operator.

Why do you feel the situation is different?

···

On 10 Oct 2016, at 17:34, Ole Begemann <ole@oleb.net> wrote:

When I declare my closure as @noescape, (the default), I explicitly tell
the compiler that, despite the fact closing over it *may* allow it to
escape, I, the programmer, guarantee that it will not.

I would understand a warning, but I don’t understand that the compiler
insists on putting out an error.

So, while the compiler could perhaps, as you say, perform more
sophisticated checks, I don’t even request that. I request however, to
be able to tell the compiler that I know better.

If I am wrong, and if I still let the closure escape despite my promise,
then I made a programming error, and my code can legitimately crash.

I wouldn't like this. I much prefer the current state where this is an error. That said, it would indeed be great if the compiler were smarter when it does the analysis whether a closure actually escapes.

There is already a bug report for this, by the way: SR-2274 [1]

[1]: https://bugs.swift.org/browse/SR-2274

This is no different than using the “!” to promise the compiler that an
optional is not nil: if I don’t keep my promise, the program crashes.

Last, in presence of a similar warning (instead of an error), a simple
way to squelch the warning would be to make the @noescape declaration
explicit.
This would require un-deprecating it.

I now feel that I should propose that as an evolution.

What do you think?

Again, I don't think we should go that route. I prefer the status quo.


(Ole Begemann) #11

So I suppose you feel this case is different from the (unsafe) implicitly unwrapping of optionals with the “!” operator.

Why do you feel the situation is different?

I agree that it's a somewhat similar situation, at least if the function declaration were made explicit with a `@nonescaping` keyword as you suggested.

I just think features like force-unwrapping of optionals should remain the exception, not the rule. Just because most people will agree (I assume) that force-unwrapping is useful, doesn't automatically mean we should extend this logic to other use cases.

On a side note, part of SE-0103 [1] was the `withoutActuallyEscapingFunction` function, which would actually allow you to explicitly override the compiler, though I'm not sure it would cover the nested function situation we're discussing here. Sadly, `withoutActuallyEscapingFunction` is implemented yet [2].

[1]: https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md
[2]: https://bugs.swift.org/browse/SR-2313