Using actor inheritance/typed throws and continuations

I wanted to experiment with using the features of SE-0420 to solve some problems I've run into with withCheckedContinuation. Specifically, I have to make a function that is guaranteed not to suspend until the block of withCheckedContinuation has been run. Have I succeeded? I'm not 100% sure I know how to verify this.

func myOwnWithContinuation<T>(
	body: (CheckedContinuation<T, Never>) -> Void,
	isolation: isolated (any Actor)? = #isolation
) async -> T {
	await withCheckedContinuation { continuation in
		// is this guaranteed to execute before the *caller* is suspended as well?
		body(continuation)
	}
}

I was also trying to use typed throws to make a more general version of this system. It feels like this should be possible, but I cannot figure it out. Does this require a change to withCheckedThrowingContinuation, or is it just that I am bad at generics?

I cannot figure out how to work around the error here:

func myOwnTypedThrowsWithContinuation<T, Failure: Error>(
	body: (CheckedContinuation<T, Failure>) -> Void,
	isolation: isolated (any Actor)? = #isolation
) async throws(Failure)-> T {
	try await withCheckedThrowingContinuation { continuation in
		// Error: Cannot convert value of type 'CheckedContinuation<T, any Error>' to expected argument type 'CheckedContinuation<T, Failure>'
		body(continuation)
	}
}
2 Likes

As for that part I recall (and docs confirm) that body here is called synchronously to the caller. With the latest toolchains I believe this should be even more clear as implementation has been already updated with new #isolation instead of private attribute.

1 Like

Well look at that! It didn't even occur to me to check, but you are right! I think that's all the confirmation I need. Thanks so much!

1 Like

Yes, body is always guaranteed to execute before you suspend. Essentially the suspension happens after you return from body (or not at all if continuation.resume() has been called before you return from body).

with*Continuation hasn't been updated for typed throws yet (at least on the latest toolchain I tried) so that's why you can't get that working. Probably best to wait for that to be officially supported but you can in theory do something like this:

func myOwnTypedThrowsWithContinuation<Success, Failure: Error>(
    isolation _: isolated (any Actor)? = #isolation,
    function: String = #function,
    body: (CheckedContinuation<Success, any Error>) -> Void
) async throws(Failure) -> Success {
    do {
        return try await withCheckedThrowingContinuation(function: function) { continuation in
            body(continuation)
        }
    } catch let error as Failure {
        throw error
    } catch {
        fatalError()
    }
}

Note that the checked continuation still uses any Error since that's what we get from the stdlib API so the error type wouldn't be properly enforced by the compiler. To do better you would need to wrap the CheckedContinuation with your own type and forward to the actual continuation internally.

4 Likes

This is great, thanks so much!