The compiler forces you only sometimes to use an `async` overload of a function. Why?

Hey,
there is a strange compiler behavior that I don't quite understand.

I have a basic example: There are two methods called load(), and one of them is async.

func load() {
  print("A")
}

func load() async {
  print("B")
}

When I call load() from synchronous code, then the synchronous version of it will be used. And when I call it from an asynchronous context, the async version will be used and even enforced by the compiler:

func someMethod() {
  load() // Prints ("A")
}

func someAsyncMethod() async {
  load() // Error: Expression is 'async' but is not marked with 'await'
}

However, this is not the case when you call it from within a Task:

Task {
  load() // This compiles. Prints ("A")
}

You can optionally add the await keyword and then it will use the async version:

Task {
  await load() // This compiles. Prints ("B")
}

But you cannot do both:

Task {
  load() // Error: Expression is 'async' but is not marked with 'await'
  await load()
}

What's the logic/reasoning behind this? I guess Task is a special case or something :thinking:

Thanks! :slight_smile:

1 Like

I found the answer. It has nothing to do with Task.

The behavior is defined in SE-0296:

In non-async functions, and closures without any await expression, the compiler selects the non-async overload:

When initializing a new task Task, we pass in a closure and when there are no await expressions, the non-async overload of load will be used.

This was added in an amendment to that proposal.

2 Likes