Can we avoid all AsyncSequence properties having to throw?

There is no get rethrows. Is it coming?

To get around it, I had the thought that maybe you could do this…

public protocol AsyncThrowingSequence: AsyncSequence { }

public extension AsyncThrowingSequence {
  var collected: [Element] {
    get async throws { try await .init(self) }
  }
}

extension AsyncThrowingCompactMapSequence: AsyncThrowingSequence { }
extension AsyncThrowingDropWhileSequence: AsyncThrowingSequence { }
extension AsyncThrowingFilterSequence: AsyncThrowingSequence { }
extension AsyncThrowingFlatMapSequence: AsyncThrowingSequence { }
extension AsyncThrowingMapSequence: AsyncThrowingSequence { }
extension AsyncThrowingPrefixWhileSequence: AsyncThrowingSequence { }
extension AsyncThrowingStream: AsyncThrowingSequence { }
extension ThrowingTaskGroup: AsyncThrowingSequence { }
public protocol AsyncNonThrowingSequence: AsyncSequence { }

public extension AsyncNonThrowingSequence {
  var collected: [Element] {
    get async { try! await .init(self) }
  }
}

extension AsyncCompactMapSequence: AsyncNonThrowingSequence { }
extension AsyncDropFirstSequence: AsyncNonThrowingSequence { }
extension AsyncDropWhileSequence: AsyncNonThrowingSequence { }
extension AsyncFilterSequence: AsyncNonThrowingSequence { }
extension AsyncFlatMapSequence: AsyncNonThrowingSequence { }
extension AsyncMapSequence: AsyncNonThrowingSequence { }
extension AsyncPrefixSequence: AsyncNonThrowingSequence { }
extension AsyncPrefixWhileSequence: AsyncNonThrowingSequence { }
extension AsyncStream: AsyncNonThrowingSequence { }
extension TaskGroup: AsyncNonThrowingSequence { }
public extension Array {
  init<AsyncSequence: _Concurrency.AsyncSequence>(_ asyncSequence: AsyncSequence) async rethrows
  where AsyncSequence.Element == Element {
    self = try await asyncSequence.reduce(into: []) { $0.append($1) }
  }
}

…but no. These sequences that do not throw can wrap ones that do, e.g.:

AsyncMapSequence<
  AsyncThrowingMapSequence<Sequence, Element>,
  Element
>

…and so try! at the AsyncNonThrowingSequence level is unsafe.

What's the intended solution?

  1. Use try! at call site, when none of those nested placeholder types have Throwing in the name.
  2. Use try even though we're aware errors are impossible.
  3. Continue to avoid properties in favor of methods, as we did before properties could throw.
1 Like

I would say the problem here is that effectful properties do not support rethrows by conformance. To me this seems like a bug at best, or a hole in the proposal for effectful properties at worst.

During the design of AsyncSequence this was one of the investigation paths I looked at, and the combinatorial explosion it causes is pretty gnarly. Hence why we chose to go the route of conformance based rethrows.

https://github.com/apple/swift-evolution/blob/main/proposals/0310-effectful-readonly-properties.md states that

The rethrows specifier is excluded from this proposal because one cannot pass a closure (or any other explicit value) during a property get operation.

Which is not true in the realms of conformance based rethrows.

1 Like

@kavon Is this something that would perhaps be worthwhile to revisit? Or do you feel it is just an oversight that is a bug?

I don’t believe “conformance based rethrows” ever came to review, did it? If so, then the text isn’t wrong, and since it’s an undocumented feature, its behavior can be changed as needed—after all, one cannot review a revision to a feature that hasn’t itself been reviewed.

3 Likes

hmm that is correct; it only was done as a pitch: [Pitch] Rethrowing protocol conformances but never past that.

I know there was a flurry of things going on, @Douglas_Gregor, but what do you think the proper course of action is here?

1 Like