Skip to content
This repository was archived by the owner on Jun 20, 2023. It is now read-only.
This repository was archived by the owner on Jun 20, 2023. It is now read-only.

using await/await using/async using #12

Closed
@rbuckton

Description

@rbuckton

In the January, 2023 TC39 plenary session there was a debate on what the final syntax for the asynchronous using declaration should be, given that there are conflicting developer intuitions regarding each potential syntax form.

The three forms currently under consideration are:

  • using await x = y (currently proposed)
  • async using x = y
  • await using x = y

We opted to postpone advancement until the March, 2023 plenary to give some delegates the opportunity to conduct informal surveys to determine which of these options more clearly represented the semantics of the proposal. If there is no clear choice, the proposal will advance to Stage 3 at the March, 2023 plenary using the proposed syntax.

There are pros and cons to each syntax option, which I will summarize below.

using await x = y

The current proposal syntax was chosen prior to the January, 2023 plenary for a number of reasons. The use of the await keyword very clearly indicates an asynchronous interleaving point, much like AwaitExpression and the for-await-of statement. In addition, the using await keyword order was chosen to avoid ambiguity with an await using expression, given that using on its own remains a valid identifier, and that await as a modifier currently only occurs to the right of the statement it modifies (i.e., for await).

However, there is the potential for confusion for code readers due to the non-local nature of the using declaration. The actual await occurs as control flow exits the block scope, rather than immediately at the site of the declaration. This may lead some to incorrectly assume that using await x = y would await the value of x/y. In the case of a for await (const x of y) statement, the for await operation doesn't await the value of y, though it does await the result of each next() operation on its iterator, which in turn means that it does await the value that becomes x. This potential conflation of meanings can lead to confusion if attempting to intuit the meaning of using await given for await as context.

async using x = y

The async using syntax form was initially suggested as a means to pair with a specially annotated await using { ... } block, so as to resolve #1. However, we were able to resolve #1 without the introduction of a new block syntax, which is why we did not choose prior to the January, 2023 plenary.

The advantage of the async using syntax is that it indicates an asynchronous operation without conveying a meaning that the operation might occur immediately. However, async using breaks with existing uses of async in the language today. Currently, async is only used to indicate functions that have different runtime semantics than a normal function, permitting the use of await expressions and the for-await-of statement in the function body. Yet this use doesn't indicate an interleaving point will occur, which breaks from the intended semantics of this proposal. This meaning of async is further reinforced by proposals like async do {}, which would still require an explicit await somewhere to observe the result.

Also, while a minor concern, async using will likely need a cover grammar to disambiguate between an async using declaration and an async using => declaration.

await using x = y

A third option we've discussed outside of plenary would be to use an await using ordering instead. This matches the C# syntax that is the equivalent of this behavior, and has the benefit of continuing to use await to indicate an async interleaving point. This also has a slight advantage over using await, given that the keyword order may lean more towards an interpretation of "await the using of x". It is unclear, however, if this distinction is enough to guide developer intuition.

As with async using, there is a minor concern regarding the need for a potential cover grammar to disambiguate await using as a declaration from await using (or await using.x) as an expression.

Not considered: using async x = y

We are not currently considering a using async keyword order at this time. We feel it is important to align the sync and async versions of the proposal, and are concerned that the ambiguity between:

using async x = y; // async 'using' of 'x' identifier
using async = y; // sync 'using' of 'async' identifier

would lead to further confusion. We could ban async as an identifier in using, as we've currently done for await (and for the same reason), however there is a distinction between await and async as identifiers. Currently, await is a reserved word, making it a syntax error to use it as an identifier in strict-mode code. It is also a syntax error to use it as an identifier in an async function, leaving it as only legal in non-async, non-strict code. async, on the other hand, is not reserved in any mode, nor is it reserved in async functions, so we are concerned that banning async for this purpose would get in the way of potential refactors in existing code, such as:

// source
const async = ...;
try {
  ...
}
finally {
  async.dispose();
}

// refactored
using async_1 = ...;
...

Due to the existing restrictions the language imposes on the use of await as an Identifier, we're far less concerned about using await being a refactoring hazard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions