I’m curious if anyone has run into this question before. I initially ran into problems when trying to create a non-async wrapper to the Task.sleep
, then further digging has led me to this simpler discussion point.
This is valid Swift:
struct Foo {}
extension Foo {
static func bar() async throws {
}
static func bar() {
Task {
try await bar()
}
}
}
But the following is not:
extension Task {
static func bar() async throws {
}
static func bar() {
Task {
try await bar()
}
}
}
This gives me two errors (in Xcode 15.4):
-
Referencing initializer 'init(priority:operation:)' on 'Task' requires the types 'Failure' and 'any Error' be equivalent
-
'Cannot convert value of type '()' to closure result type 'Success'
.
The latter has a Fix-it that adds a as! Success
at the end of the call to bar
that fixes the second error.
Already, I’m a bit confused about why it was needed – Foo didn’t need such a cast! Presumably it was implicitly doing a cast, but why doesn’t it do it for the Task extension? Digging into it a bit (Cmd-clicking on Task) I see that Success
and Failure
are the placeholder types for the Task
generic. But the bar
function that I’m defining in my Task extension is not using those placeholder types. Yes, there’s a Task being used inside my bar function, but I’d have thought that was independent of the generic context for the static functions signature. So why does this cause an error?
But even setting that aside, using the Fix-it-suggested cast and plowing ahead anyway, we have the first error stopping us. I find I can get rid of the error by changing the extension declaration line to:
extension Task where Failure == Error {
But I don’t like that, either. First of all, it seems too specific (but perhaps it’s syntactic sugar really meaning Failure is any type that conforms to Error, I’m not sure), and secondly, it has the same problem where I feel the Task inside the implementation of bar
should not have cared about the constraints on the extension.
Finally, getting back to my initial starting point, I try to use these iffy-feeling techniques to define a non-async sleep to Task, like so:
extension Task where Failure == Error {
static func sleep(nanoseconds: UInt64) {
Task {
try await Task.sleep(nanoseconds: nanoseconds) as! Success
}
}
}
Now I get these warnings:
No 'async' operations occur within 'await' expression
No calls to throwing functions occur within 'try' expression
Which makes me think that the compiler is doing a recursion rather than calling the standard async version of sleep. Obviously that would be very bad and is not what I’m intending. How do I fix this?
I know I’m somewhat off the beaten path here, and I know there are many simpler ways of solving the problem of making a non-async wrapper to Task.sleep
, but in the interest of deepening my understanding of Swift and related frameworks I’m asking if anyone has insight into this situation.