Why can't I pass certain expressions to a closure parameter marked @convetion(block)?

Viewed 163

I discovered this weird behaviour:

Consider the following code:

func foo(_ x: () -> Void) {

}

class Bar {
    func bar() {
        print("hello")
    }
}

var bar: Bar? = Bar()

These compiles:

foo(bar?.bar ?? {})

but this does not:

DispatchQueue.main.async(execute: bar?.bar ?? {})

It produces the error:

Cannot convert value of type '() -> ()' to expected argument type '@convention(block) () -> Void'

Now if I change bar to:

func bar() -> (() -> Void)? {
    return { print("hello") }
}

and then do:

DispatchQueue.main.async(execute: bar() ?? {})

It compiles!

So it's definitely not because of the ?? operator

Question: Can you explain this behaviour?

Everything below this point is my experimentation.


I spotted that the only difference between the async method and foo is that the execute parameter is marked with @convention(block).

Hypothesis: I cannot pass optional chaining expressions into parameters marked with @convention(block).

But this seems to be wrong, as I can do this and it still doesn't compile:

DispatchQueue.main.async(execute: Bar().bar ?? {})

It also seems like I can cast the expression to a () -> Void and it magically compiles:

DispatchQueue.main.async(execute: (bar?.bar ?? {}) as () -> Void)

Isn't bar?.bar ?? {} already of type () -> Void? This makes no sense!

Another way to make it compile is to extract bar?.bar as a local variable:

let barFunc = bar?.bar
DispatchQueue.main.async(execute: barFunc ?? {})

which makes even less sense...

0 Answers
Related