After talking with @Douglas_Gregor a bit more I think we are in agreement this is a sensible approach here. I will have this updated on the proposal to reflect the following:
public struct AsyncThrowingStream<Element, Failure: Error> {
public struct Continuation: Sendable {
public enum Termination {
case finished(Failure?)
case cancelled
}
/// * See AsyncStream.Continuation.yield(_:) *
public func yield(_ value: Element)
/// Resume the task awaiting the next iteration point with a terminal state.
/// If error is nil, this is a completion with out error. If error is not
/// nil, then the error is thrown. Both of these states indicate the
/// end of iteration.
///
/// - Parameter error: The error to throw or nil to signify termination.
///
/// Calling this function more than once is idempotent. All values received
/// from the iterator after it is finished and after the buffer is exhausted
/// are nil.
public func finish(throwing error: Failure? = nil)
/// * See AsyncStream.Continuation.onTermination *
public var onTermination: (@Sendable (Termination) -> Void)? { get nonmutating set }
}
/// * See AsyncStream.init *
public init(
_ elementType: Element.Type,
maxBufferedElements limit: Int = .max,
_ build: (Continuation) -> Void
) where Failure == Error
}