Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0520" in the subject line.
Trying it out
The toolchain with the latest implementation is currently building - @ktoso will update the review thread with the toolchain links for macOS, Linux, and Windows when they're ready.
What goes into a review?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:
What is your evaluation of the proposal?
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
More information about the Swift evolution process is available at:
Yeah generally it's better to model things with general features, agreed.
The way I've implemented this is with an attribute that basically says "if there is an parameter with a closure, and it throws, assume we must not discard the result even though it's not re-throwing". It COULD be generalized in the future to make it into an official attribute as such behavior is common for any "future-like" type, like Task. So it's kinda niche, but not necessarily tied to Task, and I could see other libraries making use of it (basically an extension of @discardableResult).
So I wouldn't mind the extra work to pitch an official spelling for this in the future. Since we're a bit late for the 6.4 release I'd suggest reviewing this independently though.
Is it not possible to do this via the existing overloads, from before typed throws? Admittedly that doesn't account for throws(Never), but that's a bit of an edge case anyway.
Part of the goal here is to reduce the number of overloads. It's gotten so bad that we had to use gyb to generate the sheer number of overloads of task initializers and task groups in the past.
Adopting typed throws has a side-benefit of being able to kill off this gyb generation and converge on less overloads: the de-gybification is in progress over here, and isn't really tied to the proposal semantics itself, but since you asked why we're doing it this way and not another way, that's a contributing factor.
I continue to believe that you don't generally want to discard task handles, aside from possibly "dispatch_async-like" cases (synchronous after an initial actor hop).
That said, this is clearly better than the status quo, and nothing prevents adding warnings in more cases in future, so definitely +1.
If this does eventually get an official spelling, perhaps it could take the form of a where clause on @discardableResult. So the Task initializer could have @discardableResult(where Failure == Never).
But since for now, this proposal introduces magic, I think it could be worthwhile to extend the magic to also account for non-Void and non-@discardableResult closures. That is, there could be another compiler-internal attribute that says "if there is a parameter with a closure, and the closure returns Voidor it has an implicit return from a single expression that calls a @discardableResult function".
I think we want the latter extension to use typed throws like so:
extension Test {
init(operation: () throws(Failure) -> Success) {}
}
But that also compiles just like your mock, and the point remains and I agree only Task<Void, Never>.init should be @discardableResult.
But there's a practical question of whether the code churn of having to type _ = ... in non-throwing cases too would be too large. For instance, one practical issue with @discardableResult is that it doesn't get forwarded in cases like:
let (stream, continuation) = AsyncStream.makeStream(of: Int.self)
Test { // โ ๏ธ Result of 'Test<Success, Failure>' initializer is unused
continuation.yield(42)
}
forcing to prepend either the inner or the outer expression with _ =.
That's not my point. In this case, my code wanted to form a Task<Void, Never> (or your mock Test<Void, Never>) from a non-throwing expression, but due to yield(_:) returning a @discardableResult itself, the type system actually made it a Task<AsyncStream<Int>.Continuation.YieldResult, Never>, which by your proposed new rules would no longer be @discardableResult itself.
I also think the API would be clearest if only Task<Void, Never> was @discardableResult, and all other types of unstructured Tasks (throwing, or non-throwing but value-returning) would give the warning unless explicitly discarded.
But I'm trying to point out that there may be cases where the community may still want to keep Task<_, Never> also @discardableResult for all other Success types than Void, as well.
Yeah, I see. One example I had in mind was items.removeLast() which returns the item which would have to be silenced with _ = if it's unwanted.
In case of your example it could be silenced with try! continuation.yield(42).checkResult() to not get that throw propagated outside (with the side benefit that if it does throw โ you'd quickly know about that).
I'd love that as well... even to the point if discardableResults returning functions were not be able participating in the "single (or last) expression don't need return" machinery and require explicit return statements to make intent clear (as pitched here)
Thinking out loud: what if discardable was on type? And thus propagable along with the type..
An unfortunate development here is that we're not able to use typed throws in this API (yet), as doing so has caused a significant flood of source compatibility regressions. We'll have to solve these implications of typed throws before this API will be able to adopt it. (typed throws adoption was reverted).
This discussion should continue about just "throws" but the spirit of the proposal remains the same.