"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.

It looks like many concrete types adopting AsyncSequence conditionally adopt Sendable when the Element is Sendable:

The AsyncStream is Sendable when the Element is Sendable:

But the AsyncStream.Iterator explicitly tells us it is not Sendable:

I see no place where AsyncStream.Iterator is then marked as Sendable when Element is Sendable. I also see no place where AsyncStream.Iterator is ever made SendableMetatype.

Does anyone understand how AsyncStream might be preventing this warning from displaying? Is there some kind of voodoo sorcery happening somewhere and I'm not understanding?

To put it another way… this code:

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

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

Is not giving me errors… I don't understand why there are no errors… and I don't understand to what extent I can assume there would not be errors at some point in the future if this "implicit SendableMetatype" turns out to actually be a bug instead of a feature and I actually can't assume that AsyncStream.Iterator is a SendableMetatype.

Our project has 252 new warnings about non-Sendable metatypes when building in Xcode 26b2. Some of them are AsyncSequence+AsyncIterator-related, but far from all of them.

SE-0470 says

Experiments with the prototype implementation of this feature uncovered very little code that was affected by this change

Not so much.

Essentially every generic function that creates a sending closure falls afoul of this.

1 Like

Thanks for capturing this issue. The code above is well-formed, and the compiler fix is here.

Doug

3 Likes

As of Xcode 26b3, we're down to 3 warnings, all of which look genuine.

2 Likes

As of b5, we're way back up again, including for iterating sendable async sequences :(

1 Like

Hmm… so here is an example of a successful build from 2025-08-06:

Here is the commit:

Here are the tests we landed:

But those tests pass:

...
[2025-08-06T09:30:01.985Z] PASS: Swift(macosx-x86_64) :: Concurrency/sendable_metatype_typecheck.swift (1094 of 19178)
...
[2025-08-06T10:20:11.130Z] PASS: Swift(iphonesimulator-x86_64) :: Concurrency/sendable_metatype_typecheck.swift (1261 of 19178)
...
[2025-08-06T11:17:52.017Z] PASS: Swift(watchsimulator-x86_64) :: Concurrency/sendable_metatype_typecheck.swift (1260 of 19178)

All those tests are passing on Intel… but I am seeing the warning on Apple Silicon… hmm…

AFAIK that sendableSequence test should fail unless:

  • another change has landed very recently to fix this
  • this for some reason does not fail from Intel

Hmm… the sendableSequence test:

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

seems to build without warnings from Xcode_26_beta_6.

But the nonSendableSequence test:

func nonSendableSequence<S: AsyncSequence>(_ s: S) throws {
  Task.detached {
    for try await i in s { // expected-warning{{capture of non-Sendable type 'S.AsyncIterator.Type' in an isolated closure}}
      // expected-warning@-1{{capture of non-Sendable type 'S.Type' in an isolated closure}}
      print(i)
    }
  }
}

is now an build failure error from Xcode_26_beta_6:

Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure

All base AsyncSequence (and algorithm AsyncSequences too) should all be fine to just ignore this warning for now - you can force that by using a nonisolated(unsafe) let seq = s and then passing the seq variable in instead.

@Douglas_Gregor what would it take to affix the SendableMetatype protocol to both AsyncSequence and AsyncIteratorProtocol? Is that something we could consider? As Swift Concurrency has changed from the initial implementation the meaning and role of AsyncSequence has changed slightly such that looking at it today may have actually required the type to be Sendable. (if not possible ~Copyable but that is a different story).

3 Likes

Hmm… I don't believe I understand exactly what you are suggesting here.

func nonSendableSequence<S: AsyncSequence>(_ s: S) throws {
  nonisolated(unsafe) let seq = s
  Task.detached {
    for try await i in seq
      print(i)
    }
  }
}

This code still displays a warning and a build failure error from Xcode_26_beta_6.

Ahh… that still also was a build failure error from 16.4 and 6.1.2. So this is not a new build failure error AFAIK. Sorry about that!

This code:

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

Builds without warnings. Which is the issue that came back and got fixed again.