[Accepted with Modifications] SE-0388: Convenience Async[Throwing]Stream.makeStream methods

Hello Swift community,

The review of SE-0388: Convenience Async[Throwing]Stream.makeStream methods ran from February 15...26, 2023.

Feedback was overwhelmingly positive on the general idea, but there were two significant points of controversy: what the makeStream(of:) methods' result type should be called, and whether a tuple result type should be used instead. The language workgroup has decided to accept the proposal with modifications to use an anonymous tuple result type.

The workgroup concluded that a tuple was more appropriate for several reasons:

  • The result type struct merely holds the two values without adding any real semantics to them, and we see little prospect of it being extended in the future with additional members or conformances. We believe this is why nobody could agree on a name for the type: the type doesn't have enough meaning to motivate any specific name.

  • In normal use, the two values returned by makeStream(of:) are immediately separated and handed to different parts of the program. Tuple destructuring makes it very easy to do this, but using a nominal type prevents destructuring.

  • Similar APIs in the standard library, such as String.decodeCString(_:as:repairingInvalidCodeUnits:), BinaryInteger.quotientAndRemainder(dividingBy:), and UnsafeMutableBufferPointer.initialize(from:), use tuples for their result types. The only counter-examples we could identify used an enum instead of a struct (UnicodeDecodingResult) or added substantial semantics that justified a new type (the never-shipped DoubleWidth integer type from SE-0104).

  • The primary motivation for using a nominal type was that it would allow the result type and each of its properties to be separately documented, which was thought to improve documentation quality. However, the language workgroup believes this would actually reduce the overall quality of the documentation by dividing information about how to use makeStream(of:) across many doc comments. If our documentation tools have limitations in their ability to describe tuple results, we should address those issues head-on instead of designing language features to work around them.

  • Using a tuple result type allows these methods to be back-deployed. Although the language workgroup gives this little weight in language design decisions, it is a nice bonus.

The workgroup therefore believes that this is exactly the kind of API where a tuple result would be appropriate. The possibility of using a tuple was extensively discussed both in the pitch (which used a tuple) and the first review, so the language workgroup has decided that a second review is not necessary to gather feedback on this modification. The proposal will be revised to reflect this change.

Thank you all for your participation in this discussion!

Becca Royal-Gordon
SE-0388 Review Manager

24 Likes

Should the failureType parameter be removed?

 extension AsyncThrowingStream {
   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
   ) where Failure == Error

It seems that only a throwing: Error.self argument is possible, because of the Failure == Error constraint.

(Or throwing: (any Error).self and Failure == any Error.)

2 Likes

Error self-conforms as a special exception:

struct E: Error { }
func foo(_ e: Error.Type = Error.self) { print(type(of: e)) }
foo(E.self) // E.Type
1 Like

But the constraint in the proposal is Failure == Error, not Failure: Error.

2 Likes

ATS can only be constructed with Failure == Error.

2 Likes

A similar issue was reported by @BrentM (closed as by-design, because it would need a "typed throws" language feature?)

But then why is there failureType a parameter?

1 Like