I'd like to pre-pitch updating the Clock protocol to have a nonisolated(nonsending) sleep, just to get people's first impression.
The Clock protocol has one method requirement, and currently it is @concurrent. This means every time clock.sleep is called it will jump to the concurrent executor, whether it needs to or not. Other parts of the standard library have gotten the nonisolated(nonsending) treatment, such as withTaskCancellationHandler and withCheckedContinuation, so why not Clock?
And if you need a more concrete reason, consider a theoretical ImmediateClock conformance that does not actually suspend when asked to sleep. Such a clock can be handy in testing code where we don't actually care about the passage of time and just want to squash all of time down to a single instant.
With such a clock, what would you expect the following to print when run in a main.swift executable?
let clock = ImmediateClock()
var count = 0
let task = Task.immediate {
while count < 1_000 {
try await clock.sleep(for: .seconds(1), tolerance: nil)
count += 1
}
}
print("count", count)
try await task.value
print("count", count)
To me, since Task.immediate runs all synchronous code immediately, and since clock.sleep does not actually suspend in ImmediateClock's implementation, I would hope the entire loop is done synchronously and "count 1000" is printed twice to the console.
But this does not happen. Instead "count 0" is printed first, and then "count 1000". This code does incur actual suspension points even though ImmediateClock implements sleep as a synchronous method. It is not possible to properly implement ImmediateClock using the Clock protocol alone. But if Clock's sleep requirement were made nonisolated(nonsending), this code would deterministically behave exactly how we want.