Thanks for the updated pitch @Douglas_Gregor !
Tasks
It'll definitely help with Task and friends a lot which are right now a bit weird in their lossy nature surrounding errors.
Documenting intended usage
I know that "we know" but I think this feature needs to become incredibly well documented that this should be used very rarely, in very specific situations (when one can guarantee no other kinds of errors will ever need to be thrown). We sometimes tend to omit detailed instructions in the TSPL, but this feature really really needs to get its own big section with explanations when not to use it. (Which then people will miss to read, but that's the best we can do heh )
I'm sure people will absolutely foot-gun themselves and lock themselves out of evolving APIs without source breaks though anyway... but I guess that's the price we're willing to pay here.
Task
creation and completion
We might want to stop and think a bit before doing this unification:
@discardableResult init(... () async -> Success) where Failure == Never @discardableResult init(... () async throws -> Success) where Failure == any Error
These two initializers can be replaced with a single initializer using typed throws:
init(priority: TaskPriority?, operation: () async throws(Failure) -> Success)
As there have been a number of requests to consider removing the @discardableResult
from the throwing versions of those initializer. It is a fair argument to say that these are sneakily hiding errors:
Task { // don't store the task
try // totally going to miss this error
}
so avoiding the unification of those inits, we could consider keeping the @discardableResult
only on the where Failure == Never
version of these initializers.
This way:
Task { try ... } // warning, dropped reference
Task { ... } // no warning, one-off shot task is not as incorrect
(ok) Throwing "some Error"
Hah, I had just written up about how that might be useful -- but then I got to the point where the proposal covers it -- all good then
Distributed actors
Yes this will help somewhat with distributed actors where authors want to THROW errors and have them be transported back to callers. In practice, people do this today but rely on as? Codable
casting of the errors. It will be nicer if we can enforce typed errors to conform to the SerializationRequirement
at compile time:
// DAS.SerializationRequirement = e.g. Codable
distributed actor Test {
typealias ActorSystem = DAS
distributed func test() throws(SpecificError) {}
distributed func test() throws(BadError) {}
// error, does not conform to DAS.SerializationRequirement
}
struct SpecificError: DAS.SerializationRequirement, Error {}
struct BadError: Error {}
call sites will work fine in practice and not source-break because of the errorUnion(SpecificError, DAS.DistributedActorSystemError) -> any Error
and if we'd ever actually form type unions here (I'd personally enjoy that, but I understand the type system implications so won't necessarily push for that), it'd also work out nicely.
(minor) Minor typos
Overall this looks good and I'll have to take a stab at integrating it into Distributed
soon I guess.