Skip to content

Add a variant of KeyPathComparator that can be used with @MainActor types #1845

@marcomasser

Description

@marcomasser

Is your feature request related to a problem? Please describe.
The following code does not compile in the Swift 6 language mode with Approachable Concurrency turned on in Xcode’s build settings:

@MainActor // Could be implicit thanks to the default actor isolation of the module.
struct Document {
    var title: String
}

// 🛑 error: Type 'WritableKeyPath<Document, String>' does not conform to the 'Sendable' protocol
let sortComparator = KeyPathComparator<Document>(\.title, order: .forward)

The issue is that KeyPathComparator must be Sendable because the SortComparator protocol it implements requires it to be Sendable. Therefore, the keyPath stored property must be of a Sendable type and that is any PartialKeyPath<Compared> & Sendable and there is no way to use a key path of an @MainActor property here.

One solution is to annotate Document with nonisolated but that is (1) not obvious on its own or from the compiler error message and (2) maybe not possible for other reasons (in my specific use case, Document is a class with lazy var properties and those cannot be nonisolated).

Describe the solution you'd like
It would be nice if it were somehow possible to make SortComparator only be conditionally Sendable since all uses in my MainActor-isolated-by-default module are only in one isolation domain and therefore don’t need Sendable conformance. In the case of KeyPathComparator, it would only be Sendable if the key path were Sendable. But apart from the issue that this would require a breaking API change, it would require the keyPath stored property to only be conditionally Sendable, which is not expressible using today’s Swift (AFAIK).

An alternative solution that (I think) should be expressible using today’s Swift would be a new non-Sendable protocol for sort comparators that sits next to SortComparator, along with non-Sendable conformances that sit next to the existing ones. But that would duplicate the API surface in this area, which is also not nice.

Additional context

All of this currently means you cannot use KeyPathComparator with types explicitly or implicitly annotated with @MainActor. This is unfortunate because the latest recommended build settings apparently are that you use MainActor default isolation for app targets, which means you cannot use KeyPathComparator in these targets without further annotations. Furthermore, SwiftUI has API like Table that support sorting using an array of KeyPathComparator, which suggest this is the API to use for this kind of thing.

The Xcode default app template also uses these settings, but it also specifies the Swift 5 language mode, which does not have expose this problem.

Some prior discussion can be found in the Swift Forum topic I created: MainActor + KeyPathComparator + class = error where @xedin wrote that it “might be worth opening an issue on Foundation even though it would be blocked on the compiler.” This is what I’m doing here 🙂

Note that there is a workaround that I mentioned in the forum post where you can add a nonisolated key path so the compiler is satisfied:

nonisolated protocol KeyPathWorkaround {}

nonisolated extension Document: KeyPathWorkaround {}

nonisolated extension KeyPathWorkaround where Self == Document {
    // Never called, so crashing is fine here:
    var title: String { fatalError() }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions