4

My question is derived from the following Japanese question. It's not my question, but I'm trying to answer the following problem but I cannot find the suitable answer.

https://teratail.com/questions/298998

The question above will be simpled like below.

func executetwice(operation:() -> Void) {
    print(operation)
    operation()
}

This compiler required to add @escaping keyword after operation: label, such as

func executetwice(operation: @escaping () -> Void) {
    print(operation)
    operation()
}

But in fact, it seems that operation block does not escape from this block.

Another way,

func executetwice(operation:() -> Void) {
    let f = operation as Any
    operation()
}

also compiler requires to add @escaping keyword. It is just upcasting to Any. In other case, just casting to same type, it seems to be error.

func executetwice(operation:() -> Void) {
    let f = operation as () -> Void //Converting non-escaping value to '() -> Void' may allow it to escape
    operation()
}

I'm not sure why I need to add @escaping keyword with no escaping condition.

Just adding @escaping keyword will be Ok, but I would like to know why the compiler required the keyword in this case.

Eric Hua
  • 976
  • 2
  • 11
  • 29

1 Answers1

1

print accepts (a variable number of) Any as arguments, so that is why it's saying that you are converting a closure to Any when you pass it to print.

Many checks are applied on closure-typed parameters to make sure a non-escaping closure don't escape (for what it means for a closure to "escape", read this):

var c: (() -> Void)?
func f(operation:() -> Void) {
    c = operation // compiler can detect that operation escapes here, and produces an error
}

However, these checks are only applied on closure types. If you cast a closure to Any, the closure loses its closure type, and the compiler can't check for whether it escapes or not. Let's suppose the compiler allowed you to cast a non-escaping closure to Any, and you passed it to g below:

var c: Any?
func g(operation: Any) {
    // the compiler doesn't know that "operation" is a closure! 
    // You have successfully made a non-escaping closure escape!
    c = operation
}

Therefore, the compiler is designed to be conservative and treats "casting to Any" as "making a closure escape".

But we are sure that print doesn't escape the closure, so we can use withoutActuallyEscaping:

func executetwice(operation:() -> Void) {
    withoutActuallyEscaping(operation) { 
        print($0)
    }
    operation()
}

Casting a closure to its own type also makes the closure escape. This is because operation as () -> Void is a "rather complex" expression producing a value of type () -> Void. And by "rather complex" I mean it is complex enough that when passing that to a non-escaping parameter, the compiler doesn't bother to check whether what you are casting really is non-escaping, so it assumes that all casts are escaping.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 1
    You should mention https://developer.apple.com/documentation/swift/2827967-withoutactuallyescaping which would be safe to use in the case of `print` (which indeed, doesn't actually escape the closure) – Alexander Oct 21 '20 at 01:21
  • @Sweeper thanks for quickly and clear answer. I had almost same suppose for this but I didn't have sufficient evidences. Your answer makes my thought clear. Thanks again. I would like to wait any other answers, and I will summarize these answers and I will post it to the Japanese board. – Tsukubadepot Oct 21 '20 at 01:28
  • @AlexZavatone thank you for additional information. I had knew that but I had not be sure that which case should I use this method. Your comment makes it clear. Just now I tried my code to check how to use withoutActuallyEscaping(_:do:). Thanks! – Tsukubadepot Oct 21 '20 at 01:42