Unexpected concurrency-related compiler warning

I have something like the following. On the let b1... line in the Task I get a compiler warning in Xcode: Capture on non-Sendable type 'PT.Type' in an isolated closure. On the let b2... line there is no warning.

I read the linked documentation on the error and tried to put a constraint on the protocol protocol P: AnyObject where P.Type: SendableMetatype but that doesn't help.

Am I doing something wrong here or is that a compiler error? What could possibly be the difference between the static class function in the protocol and the free function, that causes the warning?

I found this article about a similarly unexpected warning about a capture of non sendable type and wonder if I am hitting the same/similar issue.

Can anyone explain what's going on and what is the right solution here?

struct A: Sendable {}
struct B: Sendable {}

protocol P: AnyObject {
  nonisolated static func makeB(a: A) -> B
}

nonisolated func makeB(a: A) -> B { return .init() }

class C<PT: P> {
  ...
  @MainActor func f(p: PT, a: A) {
    ...
    Task.detached(priority: .userInitiated) {
      let b1 = PT.makeB(a: a)
      let b2 = makeB(a: a)
      ...
    }
  }
}

I believe this is what SendableMetatype is for, to ensure the type providing static functionality is Sendable too. In fact, the question mark in Xcode's warning UI should link you to the documentation telling you about it. Sendable metatypes

As an aside, you really shouldn't be using Task.detached. If you don't want that work in the current context, you can use Task { @concurrent in to move to the background.

2 Likes

Thanks. I tried it but was using it wrong:

// does not work
protocol P: AnyObject  where P.Type: SendableMetatype
// ok
protocol P: AnyObject & SendableMetatype {
  nonisolated static func makeB(a: A) -> B
}

Just in case this is helpful, you can also narrow this down to just the generic.

class C<PT: P & SendableMetatype> {
  @MainActor func f(p: PT, a: A) {
  // ...
  }
}