Pitch: Non-Discardable throwing Tasks

Something I both get wrong and also see others struggle with is ignoring the error of Task with a throwing body.

Task {
    try await somethingThatWillThrow()
    // ... ummm
    try await someOtherCriticalWork()
}

This gives you not just a way to side-step the error system, but makes it the default. This can be a pretty serious and hard-to-find problem. A common workaround is to opt-in to some more safety by using an explicit Task<Void, Never>.

This is what the signature in question looks like today:

extension Task where Failure == any Error {
    @discardableResult
    public init(priority: TaskPriority? = nil, operation: sending @escaping @isolated(any) () async throws -> Success)
}

I'd like to propose changing this to remove the @discardableResult. This would just make ignoring errors opt-in, instead of opt-out.

_ = Task {
    try await dontCareIfThisThrowsOrNot()
}

I think the warning this would introduce will also certainly result in uncovering unintentional bugs. I looked though swift-evolution/proposals/0304-structured-concurrency.md at main · swiftlang/swift-evolution · GitHub, and I didn't find a lot of justification for why this particular version should be discardable. So, given how many people have had trouble with this, I thought it could be worth revisiting.

Update: Someone was nice enough to do some work that I should have done myself and find an existing thread on this topic. Task initializer with throwing closure swallows error

47 Likes

FWIW, I just always type Task<Void, Never> now rather than just Task and any time I see Task I hear alarm bells. Seems to me like something that would be great to improve.

I think we had that pitched as part of some earlier proposal that just didn’t happen because it’s scope probably included typed throws adoption AFAIR.

Personally I’m supportive of this and it’s worth a proposal review I think.

I would probably adopt typed throws in Task.init and detached in the same proposal and call it some “improved error handling in unstructured tasks”.

Adopting typed throws in other parts of the stdlib is going to be hard but this specific api we can do right now, I think I even have it prepared on a PR tbh.

So yeah, +1 from me personally.

18 Likes

Hi! I started that original thread two years ago, and got notified when it was cross-referenced just now. :smile:

In that time, I have not changed my position on this. It would be great to get a warning for this, which is all I want, and this pitch would give me exactly that. I support this!

3 Likes

Really? That would be amazing!

This seems like a reasonable change to me.

1 Like

@mattie how about we work on a real proposal (as a PR to the evolution repo), but perhaps let's include the typed throws adoption for Task.init and Task.detached. It'd be nice if you can take the initiative on the proposal and I can provide help with the implementation and review etc.

The PR for those is here: [PoC][Concurrency] Typed throws in Task.init and .detached by ktoso · Pull Request #74110 · swiftlang/swift · GitHub

And while at it we'd do the @discardableResult part that you mentioned here. Both are pretty small changes and it'd be a bit easier to do it at once.

8 Likes

Sounds great. I'm going to work on the proposal later on this week and will post a draft.

3 Likes