Thanks for the writeup, Ben!
We thought about the throws
a bit more with @kavon as well because we're looking into polishing those semantics and we agree that the async throws
ends up not pulling its weight.
We also spent some time revising our proposed solution of "must await at-least once" (from SE-0317: Async Let - #30 by ktoso ) that we wanted to share with the thread. The original idea seems to have gotten quite some likes (if that means anything, maybe a little bit) and John seemed on board as well.
We ended iterated over the idea a bit more and arrived at the following:
-
we should ban
async let _ = hello()
- this includes any pattern binding where all bindings are
_
, i.e.async let (a, _)
is ok, butasync let (_, _)
is illegal - there really is no reason to encourage this. It does not do what it might seem at first; it is not a way for a "fire and forget"
- the created task would be immediately cancelled, leading to much potential confusion
- currently, for fire and forget, the more verbose
Task { await hello() }
must be used andasync let _
is not equivalent to that because it will wait for the child task implicitly anyway
- this includes any pattern binding where all bindings are
-
we can keep the
async let x = ...
being unused as a warning, however it must be a tailored warning specific to the fact that it's an async let, and not just some arbitrary variable.
Specifically, presently an unused async let
is just treated like a variable:
// today, just treated like a normal value
warning: initialization of immutable value 'x' was never used;
consider replacing with assignment to '_' or removing it
async let x = work()
~~~~^
We should improve the message to be:
warning: initialization of async value 'x' was never used and will be cancelled immediately;
use 'await x' to prevent the task from being cancelled immediately,
or create an un-structured task with 'Task { await work() }'
async let x = work()
~~~~~~~~~~^
warning: initialization of async value 'x' was never used and will be cancelled immediately;
use 'await x' to prevent the task from being cancelled immediately,
or create an un-structured task with 'Task { await work() }'
async let x = boom()
~~~~~~~~~~^
We can word-smith the specific warning message, but it is important that it explains that the task is immediately cancelled, and the two ways to solve this.
This seems to strike the right balance:
- developers are informed that the task gets cancelled
- the very bad idea™ of
_
assigning is forbidden, we could offer a helpful message there as for why it is so as well - developers who use warnings-as-errors would get the expected behavior here and be forced into awaiting explicitly at-least-once which is good