“Unlike other continuations in Swift, AsyncStream.Continuation supports escaping.”

the documentation for AsyncStream<T>.Continuation has a strange warning that reads:

Unlike other continuations in Swift, AsyncStream.Continuation supports escaping.

aren’t all continuations inherently escaping?

I'm also confused by this. In an earlier thread from June 2021, @John_McCall says this about a CheckedContinuation:

Escaping the continuation is the expected use pattern.

I think we might be using slightly different senses of "escaping" in those places.

A task continuation can "escape" the compiler's capacity to statically analyze how it's going to be resumed. In fact, we expect it to, because the only alternative would be to force it to be resumed within the static and synchronous duration of the withContinuation block, which would not be very useful.

Once you've resumed a task continuation, you cannot resume it again, which means its useful lifetime is bounded in time. I can see why someone would think of this as a restriction against a kind of "escape", and I think that's how the AsyncStream documentation means it, because IIUC there isn't an analogous restriction. However, I wouldn't personally call this an "escape" because I think it's better to reserve "escape" for this concept of a statically-enforceable limitation on the use of a value.

3 Likes

Thanks John.

I agree.

I didn't really understand exactly what @John_McCall meant above. I think he is saying that this is an expected use pattern:

// Is this an expected and acceptable use pattern?
    private var continuation: AsyncStream<String>.Continuation?

    lazy var outputStream = AsyncStream<String> { continuation in
        self.continuation = continuation
    }
    
    private func pushValue() {
        continuation?.yield("My String")
    }

Afaict this works and doesn't appear to be broken in any way, but it feels a little strange to me. If this is "not the right way", please let me know :slight_smile:

Also note that, in the example above, the stream is intended to always be "open", and so you never need to finish the continuation.

Edit: Nevermind, read through SE-0314 (Second review): AsyncStream and AsyncThrowingStream - #37 by yuriferretti, where it was confirmed that storing the continuation outside the closure is expected and fine.

Also found @taylorswift's other thread: `AsyncStream` constructor which also returns its Continuation

I agree that it is a rather awkward pattern, I would be in favor of either a constructor that returns the continuation too, or another primitive that is better suited for this scenario :slight_smile:

1 Like