I’m running into a situation where the compiler shows an error and I’m not quite sure if it is my code or the compiler that is incorrect. Any input would be greatly appreciated!
My project uses Swift 6, MainActor default isolation, and approachable concurrency.
Note that there seems to be a related issue that is not really fixed yet: KeyPaths are still not properly Sendable in Swift 6.2 · Issue #84983 · swiftlang/swift · GitHub
Here are the relevant pieces:
- I have a Swift UI view with a
Tableand aViewModelthat holds documents, i.e. a property of type[Document] - Usually, this
Documentwould just be astructbut because there are a couple of things that seem to be expensive, it is aclasswith somelazy varproperties. The idea is that these things will be computed once for the time theDocumentis visible in theTable. For example:final class Document: Identifiable { var id: UUID var title: String var lastViewedDate: Date private(set) lazy var formattedLastViewedDate: String = { // Do something expensive that is then shown in the table. }() } - To make sorting in the
Tablework, myViewModelalso has a property like this:var sortComparators: [KeyPathComparator<Document>] - (Not strictly relevant, but for context: The SwiftUI
Tabletakes a asortOrderparameter of typeBinding<[Sort]> where Sort: SortComparatorand I want to use theTableColumninside it that effectively requires thatSortis the specific typeKeyPathComparator<Compared>)
The issue is now with the KeyPathComparator<Document> type. Here’s what the compiler says when I try to create those:
var sortComparators: [KeyPathComparator<Document>] = [
// 🛑 error: Type 'ReferenceWritableKeyPath<Document, String>' does not conform to the 'Sendable' protocol
.init(\.title, order: .forward),
// 🛑 error: Type 'ReferenceWritableKeyPath<Document, Date>' does not conform to the 'Sendable' protocol
.init(\.lastModifiedDate, order: .forward),
// Interestingly, there is no error here!
.init(\.id, order: .forward)
]
The KeyPathComparator initializer takes a parameter of type any KeyPath<Compared, Value> & Sendable, so that must be Sendable. And that’s the reason why I’m not sure if it’s a compiler issue: Should a key path to a property in a @MainActor class be Sendable? I’m not entirely sure. If the answer is no, the issue is clearly in my code and KeyPathComparator is practically unusable. That’s why I suspect it might be a compiler issue after all.
Note: The error goes away when specifying no default isolation or when using the Swift 5 language mode.
I tried this in Xcode 26.3 (Swift 6.2.4), Xcode 26.4 beta 3 (Swift 6.3), and the latest development snapshot (swift-DEVELOPMENT-SNAPSHOT-2026-03-16-a-osx.pkg) and they all behave the same.
But wait, there’s more! I found a workaround that makes this compile:
I was curious to find out why there was no error when creating the key path to the the id property and I noticed that removing the Identifiable conformance from Document makes that missing error appear. So I tinkered around a bit and I think the reason is that Identifiable is nonisolated, which makes the compiler happy to create a Sendable key path for some reason. So I simply defined my own nonisolated protocol and made my Document conform to that and that “fixes” the compiler error.
Here’s a minimal complete reproducer:
import Foundation
/// This is a class because we use `lazy var` to avoid recomputation of some things that are ony calculated for documents that are actually visible in the `Table`.
final class Document: Identifiable {
var id: UUID = .init()
var title: String = ""
}
let sortComparators: [KeyPathComparator<Document>] = [
// 🛑 error: Type 'ReferenceWritableKeyPath<Document, String>' does not conform to the 'Sendable' protocol
.init(\.title, order: .forward),
// Interestingly, there is no error here. Presumably because `id` is defined in the `nonisolated Identifiable` protocol:
.init(\.id, order: .forward)
]
// If this code is enabled, the build succeeds.
// Presumably this has to do with the fact that the compiler
// can see a `nonisolated` key path to these properties?
#if false
nonisolated protocol KeyPathWorkaround {}
nonisolated extension Document: KeyPathWorkaround {}
nonisolated extension KeyPathWorkaround where Self == Document {
// Never called, so crashing is fine here:
var title: String { fatalError() }
}
#endif
(Interestingly, the code behaves differently when switching Document from a class to a struct: The compiler error will show for the \.id key path, regardless of whether the workaround is present or not).