[Pitch] `async let` capture list

There is currently a deficiency in Swift's async let syntax that does not allow for capturing variables to send to the right-hand side of the equals. This is especially important for Non-Sendable types that must be translated into a Sendable type to work with on the right-hand (async) side.

Take the following illustrative example that registers three new tokens using a task group. Each single-use token is captured and isolated for use only inside its child task. There is no confusion about why the token is created, where it will be used, or who has access to it:

protocol Registry: Sendable {
	associatedtype SingleUseToken: Sendable
	func register(_ token: SingleUseToken) async throws
}
protocol UniqueTokenProvider {
	associatedtype Token
	mutating func nextToken() -> Token
}
extension Registry {
	func registerThreeNewTokens<P>(using tokenProvider: inout P) async throws where P: UniqueTokenProvider, P.Token == SingleUseToken {
		try await withThrowingDiscardingTaskGroup { taskGroup in
			taskGroup.addTask { [
				singleUseToken = tokenProvider.nextToken(),
			] in
				try await register(singleUseToken)
			}
			taskGroup.addTask { [
				singleUseToken = tokenProvider.nextToken(),
			] in
				try await register(singleUseToken)
			}
			taskGroup.addTask { [
				singleUseToken = tokenProvider.nextToken(),
			] in
				try await register(singleUseToken)
			}
		}
	}
}

Consider the same extension implemented using async let which would require fewer closures and indentations. Because everything to the right of the equal sign is performed asynchronously - including capture lists in a closure and parameters passed to a function - the scope of the tokens can not be constrained to the individual calls and must be exposed to all following lines of code at the call-site scope:

extension Registry {
	func registerThreeNewTokens<P>(using tokenProvider: inout P) async throws where P: UniqueTokenProvider, P.Token == SingleUseToken {
		let singleUseToken1 = tokenProvider.nextToken()
		let singleUseToken2 = tokenProvider.nextToken()
		let singleUseToken3 = tokenProvider.nextToken()
		async let r1 = try await register(singleUseToken1)
		async let r2 = try await register(singleUseToken2)
		async let r3 = try await register(singleUseToken3)
		_ = try await (r1, r2, r3)
	}
}

I would like to propose an optional capture list following the async of async let that limits the scope of captured variables to the right-hand side of the equals. With a capture list the async let implementation could be written:

extension Registry {
	func registerThreeNewTokens<P>(using tokenProvider: inout P) async throws where P: UniqueTokenProvider, P.Token == SingleUseToken {
		async [
			singleUseToken = tokenProvider.nextToken(),
		] let r1 = try await register(singleUseToken)
		async [
			singleUseToken = tokenProvider.nextToken(),
		] let r2 = try await register(singleUseToken)
		async [
			singleUseToken = tokenProvider.nextToken(),
		] let r3 = try await register(singleUseToken)
		_ = try await (r1, r2, r3)
	}
}

An alternative solution that was discussed suggested special-casing the capture list of a right-hand closure. This would result in the following syntax that is also acceptable but less desirable for the reasons put forth in the discussion:

extension Registry {
	func registerThreeNewTokens<P>(using tokenProvider: inout P) async throws where P: UniqueTokenProvider, P.Token == SingleUseToken {
		async let r1 = { [
			singleUseToken = tokenProvider.nextToken(),
		] in
			try await register(singleUseToken)
		}()
		async let r2 = { [
			singleUseToken = tokenProvider.nextToken(),
		] in
			try await register(singleUseToken)
		}()
		async let r3 = { [
			singleUseToken = tokenProvider.nextToken(),
		] in
			try await register(singleUseToken)
		}()
		_ = try await (r1, r2, r3)
	}
}