"Capture of non-sendable type" concurrency warning from AsyncSequence AsyncIterator?

I'm seeing a confusing concurrency warning from AsyncSequence when building from Xcode 26 and Swift 6.2.

This builds with no warnings from 6.1:

func f1<Element: Sendable>(_ s: AsyncStream<Element>) throws {
  Task {
    for try await i in s {
      print(i)
    }
  }
}

func f2<S: AsyncSequence & Sendable>(_ s: S) throws {
  Task {
    for try await i in s {
      print(i)
    }
  }
}

But f2 is a warning from 6.2:

Capture of non-sendable type 'S.AsyncIterator.Type' in an isolated closure

This seems to be a workaround:

func f3<S: AsyncSequence & Sendable>(_ s: S) async throws {
  for try await i in s {
    print(i)
  }
}

func f4<S: AsyncSequence & Sendable>(_ s: S) throws {
  Task {
    try await f3(s)
  }
}

But I'm not sure I understand exactly why that workaround is needed and what could be wrong about the original code.

And then this makes this more confusing:

func f5<T: Sendable>(_: T.Type) {
  
}

func f6<Element: Sendable>(_: Element.Type) {
  f5(AsyncStream<Element>.AsyncIterator.Type.self)
}

This compiles with no warnings… which seems to imply that AsyncStream<>.AsyncIterator.Type is Sendable… but when I actually check the source code of AsyncStream<>.AsyncIterator.Type I can't actually see or confirm where that sendability is declared or enforced.

Any more ideas where else to look for clues? Could that Capture of non-sendable type warning be a false positive that might be fixed in a new 6.2 beta?

3 Likes

And then there's this:

protocol P {
  associatedtype S: AsyncSequence, Sendable where S.AsyncIterator: Sendable
}

struct S<Element: Sendable>: P {
  typealias S = AsyncStream<Element>
  // Type 'AsyncStream<Element>.AsyncIterator' (aka 'AsyncStream<Element>.Iterator') does not conform to the 'Sendable' protocol
}

Which seems to imply that instances of AsyncStream<>.AsyncIterator are not Sendable… but the type itself is Sendable? Is that correct?

Ahh… it actually looks like SendableMetatype might be what I needed:

This code all compiles from 6.2 without warnings:

func f1<Element: Sendable>(_ s: AsyncStream<Element>) throws {
  Task {
    for try await i in s {
      print(i)
    }
  }
}

func f2<S: AsyncSequence & Sendable>(_ s: S) throws where S.AsyncIterator: SendableMetatype {
  Task {
    for try await i in s {
      print(i)
    }
  }
}

func f3<S: AsyncSequence & Sendable>(_ s: S) async throws {
  for try await i in s {
    print(i)
  }
}

func f4<S: AsyncSequence & Sendable>(_ s: S) throws {
  Task {
    try await f3(s)
  }
}

func f5<T: Sendable>(_: T.Type) {
  
}

func f6<Element: Sendable>(_: Element.Type) {
  f5(AsyncStream<Element>.AsyncIterator.Type.self)
}

func f7<S: AsyncSequence & Sendable>(_ t: S.Type) where S.AsyncIterator: SendableMetatype {
  f5(S.AsyncIterator.Type.self)
}

protocol P {
  associatedtype S: AsyncSequence, Sendable where S.AsyncIterator: SendableMetatype
}

struct S<Element: Sendable>: P {
  typealias S = AsyncStream<Element>
}
1 Like

I thought those warnings were a bug, and I submitted Swift 6.2: undesired "Capture of non-sendable type 'T.Type'" warnings · Issue #82116 · swiftlang/swift · GitHub

I don't quite know how to remove those warnings.

2 Likes

This seems to work:

protocol MyProtocol1 { init() }
struct MyStruct<T> {
    var x: @Sendable () -> T.Type
}
func f<T: MyProtocol1 & SendableMetatype>(_ type: T.Type) {
    MyStruct { T.self }
}

protocol MyProtocol2 {
    associatedtype Value
}
func f<T: MyProtocol2 & SendableMetatype>(
    _ type: T.Type,
    closure: @escaping @Sendable (T.Value.Type) -> Void
) {
    Task {
        closure(T.Value.self)
    }
}

AFAIK SendableMetatype requires the 6.2 toolchain… so if you need that code to also compile for 6.1 then I guess you need some conditonal compilation.

1 Like

I'm not sure those warnings are to be expected until one opts in for isolated conformances with the experimental features IsolatedConformances and/or StrictSendableMetatypes from SE-0470 (which I did not).

3 Likes

Hmm… it looks like this is expected behavior in 6.2. I guess maybe the flags were for enabling the feature on main before the proposal was accepted.