# \[Pitch #3\] Async Let

**URL:** <https://forums.swift.org/t/pitch-3-async-let/48336>\
**Category:** Pitches\
**Tags:** concurrency\
**Created:** [May 13, 2021, 10:57pm UTC](https://forums.swift.org/t/pitch-3-async-let/48336 "2021-05-13T22:57:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 13, 2021, 10:57pm UTC](https://forums.swift.org/t/pitch-3-async-let/48336/1 "2021-05-13T22:57:03Z")

</div>

Hello everyone,  
We separated out and updated the `async let` proposal with the latest semantics and spelling details.

This is the first pitch of the feature as a stand-alone thing, it was previously pitched as part of structured concurrency [pitch #1](https://forums.swift.org/t/concurrency-structured-concurrency/41622), and [pitch #2](https://forums.swift.org/t/pitch-2-structured-concurrency/43452) and was separated out later on as it's a feature large enough to deserve it's own discussion.

It is partially implemented on today's `main` branch and available in nightly snapshots.

* * *

Changes since the original pitch are mostly focused around additional discussion and details around exact semantics of the feature, e.g. we added:

- longer discussion of throwing semantics
  - this is an area we'd welcome discussion of. I.e. currently if an async let has a throwing initializer, and is _not_ awaited explicitly, the task is awaited on implicitly but the thrown error is silently dropped. This is equivalent to TaskGroup semantics and in line with "since you didn't await on it, you clearly don't care about it" however it may have some tricky implications.

- more details about cancellation handling

[Read the full proposal here.](https://github.com/ktoso/swift-evolution/blob/d44f5dc31fa91c7e029ae9c17a5256af0c1a91aa/proposals/mmmm-async-let.md)

Please use this thread for feedback for the feature, and the [PR to Swift Evolution](https://github.com/apple/swift-evolution/pull/1362) for fixing typos or minor wording mistakes. Thank you!

---

<div class="post-metadata">

**Author:** ![omochimetaru](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/omochimetaru/32/9598_2.png) [@omochimetaru](https://forums.swift.org/u/omochimetaru)\
**Post date:** [May 14, 2021, 12:11am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/2 "2021-05-14T00:11:06Z")

</div>

Did `TaskGroup.spawn` change to `TaskGroup.async` from this last review which still haven't officially concluded? ↓

> [@SE-0304 (2nd review): Structured Concurrency](https://forums.swift.org/t/se-0304-2nd-review-structured-concurrency/47217/):
>
> Hello, Swift community. The second review of [SE-0304: Structured Concurrency](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md)begins now and runs through April 16th, 2021. The [first review](https://forums.swift.org/t/se-0304-structured-concurrency/) received largely positive comment, but led to a number of revisions, particularly regarding naming of methods. You can find a diff for the revision [here](https://github.com/apple/swift-evolution/compare/9b5e0cbd552b4c8b570aedcb94c0cb72b9f591b0..309f60fcb4f0ad4e1412adb1d0ee9ccaad0419c1#diff-6e3f26a7c1e2c41a13bcf34ef4c7d84625339b2898702f5e0bed0d6e05f1a778). This review is part of the large [concurrency feature](https://forums.swift.org/t/swift-concurrency-roadmap/41611), which we are reviewing in several parts. While we've tried to make it independent of other concurrency proposals that have not yet been reviewed, it…

I like `spawn` keyword that align with `TaskGroup.spawn` and explicitly describes that spawn execution process which runs in parallel.  
The word `async` doesn't have any hints about this execution flow.  
With language specs accepted previously, `async` is serial execution model with `await`.

---

<div class="post-metadata">

**Author:** ![adamkemp](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamkemp/32/3017_2.png) [@adamkemp](https://forums.swift.org/u/adamkemp)\
**Post date:** [May 14, 2021, 12:28am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/4 "2021-05-14T00:28:39Z")

</div>

What happens when I change this code:

```swift
// func doOtherStuff() async;

func doStuff() async {
    await doOtherStuff()
}

```

to this:

```swift
func doStuff() async {
    async let t = doOtherStuff()
    await t
}

```

Assume all of this code is in a class with `@MainActor` (i.e., we care which executor it runs on and want to stay on that executor both in `doStuff` and in `doOtherStuff`).

I think if those two code blocks do different things then this feature is going to be extremely confusing. Also, if those do different things then we're _missing_ a feature that allows you to separate the initial call from the await _without_ changing the executor that the call is run on.

`async let` would be extremely useful if it _only_ let you separate the await from the initial call without otherwise changing where the code executes. Using it as a way to implicitly detach the whole call and move it to another (unspecified) executor seems like just a confusing and unnecessary feature.

---

<div class="post-metadata">

**Author:** ![michelf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michelf/32/1180_2.png) [@michelf](https://forums.swift.org/u/michelf)\
**Post date:** [May 14, 2021, 12:33am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/5 "2021-05-14T00:33:00Z")

</div>

#### Should it be required to always `await` any `async let` declaration?

That seems impossible to enforce if we allow `async let` to be passed to closures: closures may never be executed but you generally don't know that statically.

I think it's fine to cancel the task and discard the result, whatever it might be. If the task gets cancelled (and actually check for cancellation to stop early), whether it would report an error, and the kind of error it reports (cancellation error or something else) is racy and unreliable.

Although, definitely make it a warning if the `async let` value is not awaited on any code path (like for unused variables).

#### Implicit `async let` awaiting

I wonder about the semantics for a case like this:

```swift
func go() async { 
  async let f = fast() // 300ms
  async let s = slow() // 3seconds
  if Bool.random() {
     await print(f)
  } else {
     await print(s)
  }
  return "nevermind..."
}

```

If you look at ARC, Swift will happily deallocate referenced objects as soon as they're no longer used by the current code path. If we were to follow a similar optimization pattern here we'd get this:

```swift
func go() async { 
  async let f = fast() // 300ms
  async let s = slow() // 3seconds
  if Bool.random() {
     // implicitly: cancels s?
     await print(f)
  } else {
     // implicitly: cancels f?
     await print(s)
  }
  return "nevermind..."
  // implicitly: await f
  // implicitly: await s
}

```

But the proposal seems to say it'll cancel only when reaching the end of the scope. Is early cancellation desirable? Is there a reason to do things differently from ARC?

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 1:06am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/6 "2021-05-14T01:06:39Z")

</div>

I answered a bit too quickly here, let me revise my post a bit.

> [@omochimetaru](#):
>
> Did `TaskGroup.spawn` change to `TaskGroup.async` from this last review which still haven't officially concluded? ↓

Right yeah this is a change after the 2nd review of the structured concurrency proposal indeed.  
It didn't yet get a proper summary writeup but as Ben mentions, we are working on revisions based on the review:

> [@SE-0304 (2nd review): Structured Concurrency](https://forums.swift.org/t/se-0304-2nd-review-structured-concurrency/47217/122):
>
> The proposal authors are making some adjustments as the result of feedback – the intention is to put it into a 3rd review shortly.

You can already view all the changes for the 3rd review of structured concurrency over here: [[Struct Concurrency] Update for 3rd review by ktoso · Pull Request #1345 · apple/swift-evolution · GitHub](https://github.com/apple/swift-evolution/pull/1345/files#diff-6e3f26a7c1e2c41a13bcf34ef4c7d84625339b2898702f5e0bed0d6e05f1a778R1475)

--

So, why `async`?

The spawn word came up because of the analogy with "spawning threads" and it read quite nicely with `group.spawn`.

Sadly, it did not read well in other contexts. Most notably, `spawn let` was not a spelling we felt comfortable with, and the previous spelling of `async let` really looks much more awesome and was met with some enthusiasm in prior reviews.

Because of that, I and the other proposal authors (@Douglas_Gregor @John_McCall and @Joe_Groff ) would want to keep the `async let` spelling. We also introduce the new, and very important, `async {}` operation in the 3rd structured concurrency review ([[Struct Concurrency] Update for 3rd review by ktoso · Pull Request #1345 · apple/swift-evolution · GitHub](https://github.com/apple/swift-evolution/pull/1345/files#diff-6e3f26a7c1e2c41a13bcf34ef4c7d84625339b2898702f5e0bed0d6e05f1a778R1475)), which makes for an odd situation:

- `group.spawn` - only the group uses the spawn word
- `async let`
- `async {}`
- `detach`

Because we have a mix of different words to mean creating a task. Given that spawn is the odd one here, we decided that we can stick to using the word `async` consistently to mean creating a child task, this way we arrive at a consistent API in all contexts:

- `group.async { }`
- `async let`
- `async {}`
- and `asyncDetached {}`

We can say that "`async` creates async tasks" and that's always correct. Specifically, the group's async and `async let` create _child_ async tasks. And the other APIs create async tasks which are not child tasks. Overall we feel this is a simple rule to follow, and does not introduce yet another new word into the swift concurrency vocabulary.

Hope this helps!

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 1:24am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/7 "2021-05-14T01:24:58Z")

</div>

> [@michelf](#):
>
> ```swift
> func go() async { 
> async let f = fast() // 300ms
> async let s = slow() // 3seconds
> if Bool.random() {
> await print(f)
> } else {
> await print(s)
> }
> return "nevermind..."
> }
> 
> ```
> 
> If you look at ARC, Swift will happily deallocate referenced objects as soon as they're no longer used by the current code path. If we were to follow a similar optimization pattern here we'd get this:

I think it might be a bit too weird to be so aggressive with cancellations.

I think the analogy to get inspired from is more task groups and general structured aspects of concurrency rather than ARC specifics. We do differ in cancellation _a little bit_ from task groups, in the sense that task groups never _implicitly cancel on normal return_ (they only do so if you throw out of the `withTaskGroup`).

So while it is not implemented directly "as" a task group, it should exhibit the same semantics as if we wrote it like that:

```swift
func go() async { 
  let f = withTaskGroup { g_f in 
    g_f.async { await fast() } // 300ms 

    let s = withTaskGroup { g_s in   
      g_s.async { slow() } // 3seconds
      if Bool.random() {
         await g_f.next()!
      } else {
         await g_s.next()!
      }
    } // end g_s, always await all g_s tasks
  } // end g_f, always await all g_f tasks
  return "nevermind..." // total time always 3 seconds
}

```

We make the difference between automatically cancelling not awaited tasks in `async let` but not doing so in groups because in groups you cannot easily say _which_ value you are not awaiting on. There is no way to express that in groups. But the general "end of scope" idea carries over.

With `async let` not awaiting on something sends a clear signal that you did not care about its result, so at the end of the scope–where a task group would automatically await–an `async let` also automatically awaits but also cancels.

That's the rationale for cancelling at ends of scopes for async lets. It is a small difference between these and task groups indeed but we thought an useful one.

---

<div class="post-metadata">

**Author:** ![omochimetaru](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/omochimetaru/32/9598_2.png) [@omochimetaru](https://forums.swift.org/u/omochimetaru)\
**Post date:** [May 14, 2021, 1:40am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/8 "2021-05-14T01:40:03Z")

</div>

Thanks for information.  
I think it's a good idea to use common word "async" to names of all task generation functions.

---

<div class="post-metadata">

**Author:** ![michelf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michelf/32/1180_2.png) [@michelf](https://forums.swift.org/u/michelf)\
**Post date:** [May 14, 2021, 1:49am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/9 "2021-05-14T01:49:42Z")

</div>

> [@ktoso](#):
>
> With `async let` not awaiting on something sends a clear signal that you did not care about its result

To me, not caring about its result also means not caring about its completion.

Using a local variable for keeping an object alive doesn't work in Swift, so I would not expect it to work for keeping a task alive either.

Likewise, never reading a local variable is a warning, and similarly it should be a warning here to never await on it.

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 1:55am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/10 "2021-05-14T01:55:00Z")

</div>

Yeah but then what about:

```swift
async let _ = runRunRun() // 🏃‍♀️ 

// currently this just runs fine, and cancels at end of scope.
// should this be immediately cancelled instead?

```

Because unused variables would trigger warnings, and I might want to create a child-task to `runRunRun()` because it’s doing some side effecty thing.

This is not exactly replaceable with `async{}` because that wouldn’t be a child task and therefore is _heavier_ on allocations etc, so:

```swift
async { await runRunRun() }

```

would be the “right way” to fire-and-forget but it also is heavier on allocations — needlessly so, because we never wait for the result. This is where (I personally), really wish we had `send runRunRun()` for fire-and-forget operations 🤔

This is one of the tricky things with this design I personally struggle with, but don’t have enough production experience with async _yet_ to really know if it’s an issue or not, but it feels like it could be — fire and forget is a thing still, even in our structured world.

---

<div class="post-metadata">

**Author:** ![Paul\_Cantrell](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/paul_cantrell/32/16764_2.png) [@Paul\_Cantrell](https://forums.swift.org/u/Paul_Cantrell)\
**Post date:** [May 14, 2021, 2:22am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/11 "2021-05-14T02:22:47Z")

</div>

Love the design and philosophy of this proposal. Assorted thoughts follow.

* * *

> [@ktoso](#):
>
> We can say that " `async` creates async tasks" and that's always correct.

I really appreciate heuristics like this. Even if they don’t reveal the full structure of the abstraction, they’re a useful compass, crucial to progressive disclosure.

That heuristic isn’t quite correct though, is it? When `async` appears in a _statement_, it creates concurrency, but when it appears in a _declaration_ (or in a function type), it means “must be called in a context that can handle concurrency.”

By very loose analogy, it’s as if Swift used the same word for both `throw` and `throws`…or maybe more like if it used the same word for `throws` and `catch`.

I realize this isn’t likely to change, but it still chafes a little. I’m fairly certain it will look at first blush to Swift concurrency newcomers as though `func foo() async { … }` makes the entirety of `foo()` execute asynchronously when called. Here behold the Ghost of Heavily Upvoted Stack Overflow Questions Future.

* * *

> unlike Task.Handles and futures that it is not possible to pass a "still being computed" value to another function

In general, this makes sense under the structured concurrency model. I wonder, however whether it precludes too many common basic refactoring patterns (e.g. splitting a long method into shorter ones). Might it be possible to allow “passing a handle” to direct function calls, and allow non-escaping closures to capture them? Perhaps representing the still-concurrent value as a `() async -> T` closure, perhaps in a more sugared form?

It seems like as long as the future / handle / “still being computed” value of `x` in `async let x` is _not_ `Sendable`, then it should be safe to pass around without violating structured concurrency. What am I missing here?

* * *

This is a confusing phrasing:

> By default, child tasks use the global, width-limited, concurrent executor, in the same manner as task group child-tasks do.

At first blush, the sentence appears to be saying, “child tasks use the same executor as child tasks.” ?!

I think the intention is “By default, child tasks created with `async let` use the same executor as child tasks created with `withTaskGroup`.” Am I reading that correctly? Some wordsmithing in that spot might help.

* * *

Is there a missing bridge between the world of `async let` and the world of manually created task groups?

Suppose for example that we want to execute heterogenous tasks and there is an arbitrary number of one of those tasks. Taking the `makeDinner()` example from the proposal, suppose there are _veggieCount_ vegetables instead of exactly one, and we want to chop them all concurrently. It’s clear how to do this with the manually created task group:

```swift
return try await withThrowingTaskGroup(of: CookingTask.self) { group in
  for n in 0..<veggieCount {
      group.async {
        CookingTask.veggies(try await chopVegetables(n))
      }
  }
  group.async {
    CookingTask.meat(await marinateMeat())
  }
  …
}

```

…but now we’ve inherited all the headaches of the manual group. What if we want to keep using `async let` for `meat` and `oven` while supporting the arbitrary number of veggies?

I think I know the answer to my own question: the veggies might be an `AsyncSequence`, or we could use `withThrowingTaskGroup(Vegetable.self)` just for the veggies. Is there any disadvantage to this? Is there a situation where I want to ensure that `veggies`, `meat`, and `oven` are all siblings in the same task group? Or is this simply not a realistic concern?

---

<div class="post-metadata">

**Author:** ![michelf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michelf/32/1180_2.png) [@michelf](https://forums.swift.org/u/michelf)\
**Post date:** [May 14, 2021, 2:57am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/12 "2021-05-14T02:57:13Z")

</div>

> [@ktoso](#):
>
> ```swift
> async let _ = runRunRun() // 🏃‍♀️ 
> 
> // currently this just runs fine, and cancels at end of scope.
> // should this be immediately cancelled instead?
> 
> ```

I think it should be immediately cancelled. It's going to be cancelled at the end of the scope anyway, and since it runs in parallel it might or might not complete before it gets cancelled and you'll never know. Cancelling immediately makes whatever side effects the task has more deterministic, and thus easier to debug.

If you're actually trying to create a child task that runs in parallel but is cancelled specifically just before the scope ends, then you'll need `withExtendedLifetime` or some equivalent. I'm not sure why you would want a "fire and forget" task that must be cancelled at a specific time though.

And if you're counting on the task not being responsive to cancellation (so that it completes despite being cancelled), then I think it's misusing the concurrency features. It should never be wrong to check for cancellation and terminate early. So I say it's a good thing if you get a warning about an unused variable in this case.

---

<div class="post-metadata">

**Author:** ![hooman](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hooman/32/43536_2.png) [@hooman](https://forums.swift.org/u/hooman)\
**Post date:** [May 14, 2021, 2:59am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/13 "2021-05-14T02:59:32Z")

</div>

> [@ktoso](#):
>
> This is where (I personally), really wish we had `send runRunRun()` for fire-and-forget operations 🤔

Me too.

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 3:01am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/14 "2021-05-14T03:01:41Z")

</div>

> [@hooman](#):
>
> > [@ktoso](#):
> >
> > This is where (I personally), really wish we had `send runRunRun()` for fire-and-forget operations 🤔
> 
> Me too.

Heh, seems today isn't a good day for my brain... 🧠 Stumbling in wording and details a lot today somehow.

This actually could not be more efficient than a plain `async{}`, in order to not-await on things we can't task-local allocate the resulting task so it would be equivalent to `async{}`. I guess this only leaves some spelling sugar discussions for the future here say `async runRunRun()` but that's outside of the scope of this proposal.

---

<div class="post-metadata">

**Author:** ![QuinceyMorris](https://avatars.discourse-cdn.com/v4/letter/q/d26b3c/32.png) [@QuinceyMorris](https://forums.swift.org/u/QuinceyMorris)\
**Post date:** [May 14, 2021, 4:26am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/15 "2021-05-14T04:26:19Z")

</div>

> [@ktoso](#):
>
> we arrive at a consistent API in all contexts:
> 
> - `group.async { }`
> - `async let`
> - `async {}`
> - and `asyncDetached {}`

I find this a very weird concept of "consistent".

Let me number these for brevity of reference:

1. `group.async { }`
2. `async let`
3. `async {}`
4. `asyncDetached {}`

Both #3 and #4 are _not_ child tasks, so the significance of the word "detached" to any reasonable user of Swift is "on a different executor". The absence of "detached" in #3 means "on the same executor".

However, the absence of the word "detached" in #1 and #2 does _not_ mean "on the same executor". Those run "on a different executor".

A very weird concept of consistency!

For the sake of discussion, let's use the word "attached" to mean "on the same executor", and for the sake of discussion let's add the qualifier words to the syntax:

1. `group.asyncDetached { }`
2. `asyncDetached let`
3. `asyncAttached {}`
4. `asyncDetached {}`

Now, I'm 100% in agreement with @adamkemp that there is one vitally important thing missing from this list:

1. `group.asyncDetached { }`
2. `asyncAttached let // <-- we need this, please!`
3. `asyncDetached let`
4. `asyncAttached {}`
5. `asyncDetached {}`

I think that `asyncAttached` is just as important, and has just as many use cases as `asyncDetached`.

_This_ is a consistent list. If we want to drop any qualifier words, then "attached" seems like a good candidate:

1. `group.asyncDetached { }`
2. `async let`
3. `asyncDetached let`
4. `async {}`
5. `asyncDetached {}`

_That_ is also a consistent list, with bonus conciseness. That list would allow newcomers to reason about what they're getting in all 5 scenarios.

I understand that these are not the spellings that the authors of this pitch would prefer, but if you're going to invoke consistency as a design principle, then you should _actually_ be consistent.

I also understand that by introducing a 5th construct, I've subverted the intentions of the authors, because the most-easily-reached-for version of `async let` becomes `async/*Attached*/ let` instead of `asyncDetached let`. Personally, I don't think that's a bad thing, especially if actual consistency makes the slightly more verbose `asyncDetached` version easy to find and easy to reason about, too.

Edit: Aside from this issue, this pitch looks pretty great to me!

---

<div class="post-metadata">

**Author:** ![QuinceyMorris](https://avatars.discourse-cdn.com/v4/letter/q/d26b3c/32.png) [@QuinceyMorris](https://forums.swift.org/u/QuinceyMorris)\
**Post date:** [May 14, 2021, 4:47am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/16 "2021-05-14T04:47:09Z")

</div>

Let me add my reasoning why `asyncAttached let` (however spelled) is important to have.

According to my current understanding of the concurrency pitches, if we have code running on a concurrent executor, then `asyncDetached let` and [hypothetical] `asyncAttached let` would be more or less equivalent in most cases. IIUC, both would allow the spawning and spawned tasks to execute in parallel.

In code running on a serial executor, such as the main thread, `asyncAttached let` seems a better default choice than `asyncDetached let`, because running "on the same executor" would help to avoid data races and other thread safety issues. It should require thought and deliberate choice to introduce parallelism to this serial execution environment.

So, I see no harm and much benefit in making `async let` be the attached version.

---

<div class="post-metadata">

**Author:** ![benlings](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/benlings/32/13011_2.png) [@benlings](https://forums.swift.org/u/benlings)\
**Post date:** [May 14, 2021, 7:06am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/17 "2021-05-14T07:06:43Z")

</div>

This seems like a much cleaner separation of concurrency than is offered in the Future-based designs of .net/JS. In those cases, the concurrency is implicit by not `await`ing, whereas with this it’s explicit.

**Implicit await** - could this use the presence/absence of `@discardableResult` to require the `await`? This would then match the behaviour of synchronous returns.

**actor isolation** - I think the pitch should state explicitly what the behaviour is of an `async let` running in an isolated (global) actor context. It states that it inherits the priority and task local state, but i wasn’t clear if it also runs in the same actor isolation context.

---

<div class="post-metadata">

**Author:** ![jayton](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jayton/32/5592_2.png) [@jayton](https://forums.swift.org/u/jayton)\
**Post date:** [May 14, 2021, 9:12am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/18 "2021-05-14T09:12:48Z")

</div>

> [@ktoso](#):
>
> Yeah but then what about:
> 
> ```swift
> async let _ = runRunRun() // 🏃‍♀️ 
> 
> ```

As far as I can see, this composition of syntax is arrant nonsense and should not be allowed.

I can see that it _can_ be assigned a usable meaning, but any such meaning seems contrary to what `async let` is _for_, and the existence of `async let _` requires developers to have an unnecessarily complex mental model of how it works.

If it actually does allow better performance, that’s an opportunity for optimization of other syntax options, or possibly a new advanced-use-case runtime function.

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 9:15am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/19 "2021-05-14T09:15:48Z")

</div>

It's a bit harsh to call it nonsense, I would ask for a deeper analysis and reasoning rather. I myself have a lot of open topics to discuss here, but let's not immediately call things nonsense and leave it at that, we should weigh the tradeoffs and alternatives when saying that a piece is not desirable.

Another example to consider is what to do when one wanted to create a child task for a void returning function -- `async let _ = returnsVoid()` is an not entirely unreasonable way to spell this.

Although arguably it might be nicer to say `async returnsVoid()` just like that, but that has not been really designed and may mean the abandonment of async let entirely?

Problems with that though:

- It would also muddy the waters between `async { await returnsVoid() }` (not child task) and whatever the spelling of `<thing> returnsVoid()` would be.
- It likely means we need new words for it, yet we just came back from reducing the number of words this proposal introduces...
- it still is something we _will_ await on, unlike `async {}` so how to we make them different enough and understandable. The `spawn` word we are trying to avoid for a number of reasons - @Douglas_Gregor was digging into this a long time and has thoughts here.

---

<div class="post-metadata">

**Author:** ![Jon\_Hull](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_hull/32/678_2.png) [@Jon\_Hull](https://forums.swift.org/u/Jon_Hull)\
**Post date:** [May 14, 2021, 9:30am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/20 "2021-05-14T09:30:38Z")

</div>

I had a similar thought of requiring a @ declaration of some sort (similar to @discardableResult) to indicate that we just want to implicitly cancel/await things, and if that @ declaration wasn't there, then the compiler would force you to await or cancel explicitly (with a fixit to add the @ declaration)

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [May 14, 2021, 9:41am UTC](https://forums.swift.org/t/pitch-3-async-let/48336/21 "2021-05-14T09:41:01Z")

</div>

I believe it is a property of the call site (calling code) if they want to wait for a result or not. So making the decision if it can be not awaited on based on the declaration itself I’m not so sure about... 🤔

[Next page](https://forums.swift.org/t/pitch-3-async-let/48336.md?page=2)
