Hello everyone,
I would like to pitch a small addition to Async[Throwing]Stream to make their usage slightly more convenient. Would love to get everyones opinion on this!
Convenience Async[Throwing]Stream.makeStream methods
- Proposal: SE-NNNN
- Authors: Franz Busch
- Review Manager: TBD
- Status: Awaiting implementation
Introduction
With SE-0314
we introduced AsyncStream and AsyncThrowingStream which act as a source
AsyncSequence that the standard library offers.
Motivation
After having used Async[Throwing]Stream for some time, a common usage
is to pass the continuation and the Async[Throwing]Stream to different places.
This requires escaping the Async[Throwing]Stream.Continuation out of
the closure that is passed to the initialiser.
Escaping the continuation is slightly inconvenient since it requires a dance
around an implicitly unwrapped optional.
Proposed solution
In order to fill this gap, I propose to add a new static method makeStream on
AsyncStream and AsyncThrowingStream that returns both the stream
and the continuation.
Detailed design
I propose to add the following code to AsyncStream and AsyncThrowingStream
respectively.
extension AsyncStream {
/// Initializes a new ``AsyncStream`` and an ``AsyncStream/Continuation``.
///
/// - Parameters:
/// - elementType: The element type of the sequence.
/// - limit: The buffering policy that the stream should use.
/// - Returns: A tuple which contains the stream and its continuation.
@_alwaysEmitIntoClient
public static func makeStream(
of elementType: Element.Type = Element.self,
bufferingPolicy limit: Continuation.BufferingPolicy = .unbounded
) -> (stream: AsyncStream<Element>, continuation: AsyncStream<Element>.Continuation) {
let storage: _Storage = .create(limit)
let stream = AsyncStream<Element>(storage: storage)
let continuation = Continuation(storage)
return (stream: stream, continuation: continuation)
}
@_alwaysEmitIntoClient
init(storage: _Storage) {
self.context = _Context(storage: storage, produce: storage.next)
}
}
extension AsyncThrowingStream {
/// Initializes a new ``AsyncThrowingStream`` and an ``AsyncThrowingStream/Continuation``.
///
/// - Parameters:
/// - elementType: The element type of the sequence.
/// - failureType: The failure type of the stream.
/// - limit: The buffering policy that the stream should use.
/// - Returns: A tuple which contains the stream and its continuation.
@_alwaysEmitIntoClient
public static func makeStream(
of elementType: Element.Type = Element.self,
throwing failureType: Failure.Type = Failure.self,
bufferingPolicy limit: Continuation.BufferingPolicy = .unbounded
) -> (stream: AsyncThrowingStream<Element, Failure>, continuation: AsyncThrowingStream<Element, Failure>.Continuation) {
let storage: _Storage = .create(limit)
let stream = AsyncThrowingStream<Element, Failure>(storage: storage)
let continuation = Continuation(storage)
return (stream: stream, continuation: continuation)
}
@_alwaysEmitIntoClient
init(storage: _Storage) {
self.context = _Context(storage: storage, produce: storage.next)
}
}
Source compatibility, Effect on ABI stability, Effect on API resilience
As this is an additive change, it should not have any compatibility, stability or resilience problems. The only potential problem would be if someone has already run into this shortcoming and decided to define their own makeStream methods.
Alternatives considered
Return a concrete type instead of a tuple
My initial proposal was using a concrete type as the return paramter of the factory;
however, I walked back on it since back deployment issues were raised with introducing a new type.
I still believe that there is value in providing a concrete type since
it is easier to handle than a tuple and documentation can be provided in a nice way.
extension AsyncStream {
/// Simple struct for the return type of ``AsyncStream/makeStream(elementType:)``.
public struct NewStream {
/// The actual stream.
public let stream: AsyncStream<Element>
/// The continuation of the stream
public let continuation: AsyncStream<Element>.Continuation
@inlinable
internal init(
stream: AsyncStream<Element>,
continuation: AsyncStream<Element>.Continuation
) {
self.stream = stream
self.continuation = continuation
}
}
Do nothing alternative
We could just leave the current creation of Async[Throwing]Stream as is;
however, since it is part of the standard library we should provide
a better method to create a stream and its continuation.