Task initializer with throwing closure swallows error

If this would be a quick-fix I'd even suggest to make this first version of the 3 provided alternatives into

_ = Task {
  let thing = try await someThing()
  print(thing)
}

as it fits to the usual way we discard things that are not @discardableResult and it's just 4 little characters of "boilerplate".

Logically, I second (or x-th) the idea that the Task initializers should only be @discardable when Success == Void and Failure == Never.


Small tangent & input about what's easier to grasp for a newbie (skip if you want):

I haven't run into this in production code (thankfully) yet, but while giving a quick, ill-prepared demo I gave to "advertise" for Swift's concurrency features. There's two issues at play here:

One is obviously the type inference combined with @discardableResult. Developers new to Swift, and perhaps also to concurrency in general, are more likely to view Task as a keyword and not an actual type whose values can be stored and used later. You do not get a "thing" that you can option-click to see what the compiler deduces for you in terms of type.
Instead it just looks like a special "scope notation", just like your first main method or the top level in a playground. However, in a "normal scope" you get a compiler-note if you don't properly handle errors and it's not natural for somebody to deduce "ah, the compiler deduces a Task<Void, Error> type here, then puts any errors in the discarded value of that type", so the error is actually handled, all the convenient "implicitness" of it just hides it. That's only apparent for somebody who's already intricately familiar with Swift.

We may have become a little "operationally blind" to the fact that no matter how well we design the language and what tools we provide, concurrency is hard. Yes, requiring a more explicit discarding of errors adds "boilerplate" for all those who just know that errors inside a simple Task { ... } can be swallowed and if they are actually interested in the error they will catch or await it somehow, but I think that strides to far from Swift's safe-ness philosophy. With quick-fixes and a minimal _ = in front of those Tasks we'd fare much better, I think.

For me Swift has been an excellent tool not just to learn about more advanced stuff myself, but also to teach concepts such as concurrency (another would be value- vs reference-semantics). Every time I need to explain "ah, this is a non-obvious special case where these 5 things play together" it loses a little of teachability, though.

1 Like