`async let` and scoped suspension points

Here's a idea:

  1. No need to spell await at the end of a function scope. The function is async so there's an await in the caller at the call boundary. This await in the caller can cover our implicit await at the end of the function scope, as long as the implicit await is done after any defer block in the function.

  2. Don't await (even implicitly) when exiting most scopes (if, switch, try, catch ...). Those awaits are pushed at the end of the function scope. There's little to be gained by implicitly awaiting at every scope, and it's better to reduce the number of suspension points.

  3. An explicit await is required for loop scopes. You can use await break or await continue when there is an async let that needs to be awaited. The compiler will have a fixit for you if you forget.

    • You can still exit a loop with a simple return with no await. As long as it gets merged with the the function scope implicit await it's fine.

This way we reduce the number of suspension points and all the suspension points remain explicitly spelled with await. Maybe it makes things slightly less structured, but I think it's for the better.

Example:

func test() async {
  async let x = work(0)
  if condition {
    async let y = work(1)
  }
  for i in 2..<10 {
    async let z = work(i)
    if condition {
      return // implicitly awaits x, y, z
    }
    if condition {
      await break // awaits z
    }
    await continue // required here, awaits z
  }
  // implicitly awaits x, y
}
7 Likes