Is `sending` broken in 6.2?

I'm writing a library that wraps CoreData, and provides an async-friendly interface. One core building block I'm using to make my life easier is

    extension NSManagedObjectContext {
        func performAsync<T: Sendable, E: Error>(
            _ body: sending @escaping () throws(E) -> T
        ) async throws(E) -> T {
            do {
                return try await withCheckedThrowingContinuation { continuation in
                    self.perform {
                        continuation.resume(with: Result { try body() })
                    }
                }
            } catch {
                throw error as! E
            }
        }
    }

In Xcode 16.4 (Swift 6.1) this compiles just fine. In Xcode 26.0.1 (Swift 6.2) I get the following error:

Reference to captured parameter 'body' in concurrently-executing code

Everywhere else I use sending is also giving me warnings about capturing non-sendable values. Is this supposed to work? What do I need to change?

(Assuming this can be made to work I have a follow-up question about priority inversion due to a runtime warning I got in Xcode 16.4. But one thing at a time.)

It's very hard to reason about these things, and the diagnostics don't help at all, but I think the problem here is that the compiler doesn't know what withCheckedThrowingContinuation or self.perform (or both) do with their closures — if they call them multiple times on multiple tasks, the constraint on sending would be broken.

The correct answer is that the language is missing a feature to designate a closure as once-callable, which would allow the compiler to reason about sending (and also non-Copyable) captures.

A practical (but non-performant) workaround until that SE appears would be to put the value into a Mutex as an optional, and take it out to call it.

3 Likes

Unfortunately not, as I target iOS 13 and aligned releases, which predate both Mutex and OSAllocatedUnfairLock.

Given that Sendable doesn't actually exist at runtime, is this the part where I unsafeBitCast it to the @Sendable version?

I observed a similar behavior a while back but wasn't sure if it's by design or an omission. I have just filed #85231(Inconsistent behavior between sending parameter and captured variable). I think the issue I reported and the one in your code are probably the same issue, though my issue can be reproduced since Swift 6.0, the release in which sending was introduced, but you said that you ran into the issue only on 6.2.