[swift-evolution] async void

Yuta Koshizawa koher at koherent.org
Sun Nov 12 09:27:18 CST 2017


Sorry, I had got some confusion. Please let me retry to explain.

As you said, C# provides three kinds of async functions: `async Void`,
`async Task` and `async Task<Foo>`. All of them are necessary and
Swift should provide same functionalities.

When we think about `async/await` in Swift, because we have already
had `throws/try`, it is desired that `async/await` in Swift is
consistent with `throws/try`. So it is better to have `async/await`
without introducing a type like `Task` (or `Promise`).

Even if we employ `async/await` without `Task`, Swift has to provides
functionalities to implement "three kinds of async functions" in C#.
However if `async -> Void` in Swift works similarly to `async Void` in
C#, how can we express ones like `async Task` in C#? I think there are
two possibilities:

1. Calling `async -> Void` functions without `await` in Swift works
like `async Void` in C# and calling them *with* `await` works like
`async Task` in C#.
2. Calling `async -> Void` functions without `await` in Swift works
like `async Void` in C# and never support something like `async Task`
in C#.

I think 2 is impermissible. For example, handling completion events of
asynchronous operations without result values needs something like
`async Task` in C#. However, with 1, we lose the benefit of static
checks by the compiler. Because both of `fooAsync()` without `await`
and `await fooAsync()` are allowed, even if we want it to work like
`async Task` in C# and forget to mark `await`, the compiler tell us
nothing and it works like `async Void` in C#. It causes unexpected
behaviors. It is hard to fix such kinds of bugs. So I think
introducing `beginAsync` is better.

--
Yuta


2017-11-12 10:23 GMT+09:00 Yuta Koshizawa via swift-evolution
<swift-evolution at swift.org>:
> 2017-11-12 2:57 GMT+09:00 Adam Kemp <adam.kemp at apple.com>:
>>
>>
>>> On Nov 11, 2017, at 6:24 AM, Yuta Koshizawa <koher at koherent.org> wrote:
>>>
>>> If you replace `async` with `throws`, you can get answers.
>>>
>>>
>>>> Can you declare an async closure variable?
>>>
>>> Yes. Like `let throwingClosure:() throws -> Void = { ... }`.
>>>
>>>
>>>> Can a non-async closure be passed to a function expecting a async closure?
>>>
>>> Yes. Like we can pass `() -> Void` to a function expecting a throwing
>>> closure `() throws -> Void`.
>>>
>>> It is possible because `(Foo) throws -> Bar` is a supertype of `(Foo)
>>> -> Bar`. `(Foo) async -> Bar` is a supertype of `(Foo) -> Bar` in the
>>> same way.
>>>
>>> To treat an async function as a sync function is legal. It is similar
>>> to make a `Promise` by `Promise(value)` which is completed
>>> immediately.
>>>
>>>
>>>> Can an async closure be passed to a function expecting a non-async closure?
>>>
>>> No. `() -> Void` is a subtype of `() async -> Void`. It is same as
>>> passing `() throws -> Void` to a function expecting `() -> Void` is
>>> not allowed.
>>
>> But why not? Just asserting that it must work the same as throws
>> is not a convincing argument. You have to justify why it must work
>> that way. I think there is good reason to allow it, which I have described.
>> What reason is there to disallow it?
>
> `() async -> Void` needs to be called with `await` because it prevents
> us from forgetting handling asynchronous operations.
>
> If we use callbacks to handle asynchronous operations, it is shown to
> us by a compiler as a compilation error.
>
> ```
> func fooAsync(_ handler: () -> Void) -> Void { ... }
>
> fooAsync() // compilation error
>
> fooAsync {
>   // handles a completion event here
> }
> ```
>
> With proposed `async/await`, it is realized similarly like below.
>
> ```
> func fooAsync() async -> Void { ... }
>
> fooAsync() // compilation error
>
> await fooAsync()
> // handles a completion event here
> ```
>
> However, if async void functions work like `beginAsync`, we can easily
> forget it and it can cause unexpected behaviors.
>
> ```
> func fooAsync() async -> Void { ... }
>
> fooAsync() // OK
> // hard to know this line is executed asynchronously
> ```
>
> Readability also suffers seriously. If we don't know `bar` in the
> following code is a async function, it is impossible to expect lines
> after `baz()` are executed asynchronously.
>
> ```
> foo()
> bar()
> baz()
> qux()
> ```
>
>
>>>> It’s weird to me that we would allow you to have async void closures but not async void functions
>>>
>>> I am not sure what you mean. "async void closures" and "async void
>>> functions" have a same type. Following two are almost same.
>>>
>>> ```
>>> func foo() async -> Void { ... }
>>> let foo: () async -> Void = { ... }
>>> ```
>>
>> What started this thread is my suggestion that you should be able to write
>> an async void function. The current proposal doesn’t allow that. That’s why
>> you have to use beginAsync.
>>
>> I don’t think that makes sense. It sounds like you also think that would be strange,
>> hence your assumption that you could.
>
> By the reasons I wrote above, we need `await` even for async void
> functions for checks by compilers. Then it is required to provide a
> way to write entry points of async functions. That is `beginAsync`.
>
> --
> Yuta
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


More information about the swift-evolution mailing list