Table of Contents

Async functions

Async functions can wait for work to finish without blocking the current thread. Use them for operations such as network requests, file access, and timers.

The async modifier may appear on top-level functions, methods, and local functions. An async declaration opts the body into asynchronous control flow so await expressions can suspend and resume execution. When no return type is annotated, async declarations default to System.Threading.Tasks.Task (unit-returning async body). The compiler does not infer Task<T> from omitted return annotations.

Async functions with an explicit return type must annotate one of the supported task shapes: System.Threading.Tasks.Task or System.Threading.Tasks.Task<T>. Annotating any other type produces a diagnostic, and the compiler continues analysis as though the return type were Task. This rule applies uniformly to methods, top-level functions, and local functions declared inside other bodies. Property and indexer accessors may also carry async; getters must expose a task-shaped return type to remain valid, while setters may await asynchronous work before storing values.

Diagnostic analyzers may still suggest adding an explicit return type annotation based on observed body shape; such suggestions are advisory and do not change the language binding rule above.

Async declarations support both block bodies and expression bodies. Every return inside an async declaration completes the task produced by the method. For async Task<T> block bodies, a trailing expression statement is treated as an implicit return value and must convert to T. For async Task members each return statement must omit the expression; falling off the end of the body is equivalent to return;. For async Task<T> members, return expressions must convert to T.

Returning an existing task instance such as Task.CompletedTask is not permitted inside an async Task body. Authors must await the task to observe its completion instead of returning it directly. Attempting to return an expression from an async Task member produces a diagnostic that mirrors the behavior of C# (error RAV2705). Exceptions that escape before the first await propagate directly to the caller. Once asynchronous execution begins, await unwrapped exceptions rethrow when the task is awaited, matching .NET's observable behaviour.

Await expressions

The await keyword introduces a unary expression with the grammar await expression. Await expressions participate in the same precedence as other prefix unary operators. Because await is reserved, the identifier form must be escaped as @await when used outside this construct.

await may only appear inside an async function or lambda. File-scope statements synthesize a synchronous Program.Main plus an async Program.MainAsync, so top-level awaits run inside the async method while the bridge awaits MainAsync(args) before returning to the host. The awaited expression must expose an instance method GetAwaiter() whose return type provides an accessible IsCompleted: bool property and a parameterless GetResult() method. If the awaiter's GetResult produces no value, the await expression's type is unit; otherwise it matches the GetResult return type.

Failing any of these requirements produces a compile-time diagnostic identifying the missing member. The compiler also reports an error when await appears outside an async context.

Evaluation first computes the operand value and calls GetAwaiter() to obtain the awaiter. If val IsCompleted is true, GetResult() is invoked immediately and the await expression yields its value. Otherwise execution is suspended and later resumed when the awaiter signals completion; resumption continues after the await with the result of GetResult().