Tsukubadepot
Tsukubadepot

Reputation: 43

Converting non-escaping value to 'Any' may allow it to escape error - within not escaping function

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.

Upvotes: 4

Views: 662

Answers (1)

Sweeper
Sweeper

Reputation: 270790

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.

Upvotes: 1

Related Questions