Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0ac4d58

Browse files
grouemattt
authored andcommittedAug 17, 2021
[SE-0296] Allow overloads that differ only in async (swiftlang#1392)
* [SE-0296] Allow overloads that differ only in async * Fix typo, and enhance wording * [SE-0296] Detail resolution of overloads that differ only in async * [SE-0296] Fix typo
1 parent 8d50ec0 commit 0ac4d58

File tree

1 file changed

+38
-5
lines changed

1 file changed

+38
-5
lines changed
 

‎proposals/0296-async-await.md

+38-5
Original file line numberDiff line numberDiff line change
@@ -471,25 +471,58 @@ These two functions have different names and signatures, even though they share
471471
doSomething() // problem: can call either, unmodified Swift rules prefer the `async` version
472472
```
473473

474-
Swift's overloading rules prefer to call a function with fewer default arguments, so the addition of the `async` function would break existing code that called the original `doSomething(completionHandler:)` with no completion handler. This would get an error along the lines of:
474+
A similar problem exists for APIs that evolve into providing both a synchronous and an asynchronous version of the same function, with the same signature. Such pairs allow APIs to provide a new asynchronous function which better fits in the Swift asynchronous landscape, without breaking backward compatibility. New asynchronous functions can support, for example, cancellation (covered in the [Structured Concurrency](https://github.com/DougGregor/swift-evolution/blob/structured-concurrency/proposals/nnnn-structured-concurrency.md) proposal).
475+
476+
```swift
477+
// Existing synchronous API
478+
func doSomethingElse() { ... }
479+
480+
// New and enhanced asynchronous API
481+
func doSomethingElse() async { ... }
482+
```
483+
484+
In the first case, Swift's overloading rules prefer to call a function with fewer default arguments, so the addition of the `async` function would break existing code that called the original `doSomething(completionHandler:)` with no completion handler. This would get an error along the lines of:
475485

476486
```
477487
error: `async` function cannot be called from non-asynchronous context
478488
```
479489

480490
This presents problems for code evolution, because developers of existing asynchronous libraries would have to either have a hard compatiblity break (e.g, to a new major version) or would need have different names for all of the new `async` versions. The latter would likely result in a scheme such as [C#'s pervasive `Async` suffix](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model).
481491

492+
The second case, where both functions have the same signature and only differ in `async`, is normally rejected by existing Swift's overloading rules. Those do not allow two functions to differ only in their *effects*, and one can not define two functions that only differ in `throws`, for example.
493+
494+
```
495+
// error: redeclaration of function `doSomethingElse()`.
496+
```
497+
498+
This also presents a problem for code evolution, because developers of existing libraries just could not preserve their existing synchronous APIs, and support new asynchronous features.
499+
482500
Instead, we propose an overload-resolution rule to select the appropriate function based on the context of the call. Given a call, overload resolution prefers non-`async` functions within a synchronous context (because such contexts cannot contain a call to an `async` function). Furthermore, overload resolution prefers `async` functions within an asynchronous context (because such contexts should avoid stepping out of the asynchronous model into blocking APIs). When overload resolution selects an `async` function, that call is still subject to the rule that it must occur within an `await` expression.
483501

484-
Note that we follow the design of `throws` in disallowing overloads that differ *only* in `async`:
502+
The overload-resolution rule depends on the synchronous or asynchronous context, in which the compiler selects one and only one overload. The selection of the async overload requires an `await` expression, as all introductions of a potential suspension point:
485503

486504
```swift
487-
func doSomething() -> String { /* ... */ } // synchronous, blocking
488-
func doSomething() async -> String { /* ... */ } // asynchronous
505+
func f() async {
506+
// In an asynchronous context, the async overload is preferred:
507+
await doSomething()
508+
// Compiler error: Expression is 'async' but is not marked with 'await'
509+
doSomething()
510+
}
511+
```
512+
513+
In non-`async` functions, and closures without any `await` expression, the compiler selects the non-`async` overload:
489514

490-
// error: redeclaration of function `doSomething()`.
515+
```swift
516+
func f() async {
517+
let f2 = {
518+
// In a synchronous context, the non-async overload is preferred:
519+
doSomething()
520+
}
521+
f2()
522+
}
491523
```
492524

525+
493526
### Autoclosures
494527

495528
A function may not take an autoclosure parameter of `async` function type unless the function itself is `async`. For example, the following declaration is ill-formed:

0 commit comments

Comments
 (0)
Please sign in to comment.