Skip to content

Library protocols can not overtake the default isolation of the importing module #82249

Open
@groue

Description

@groue

Description

Swift 6.2 ships with two important features:

Now, some libraries define protocols that are intended to be used in library-controlled isolation, which may not be the default isolation of the importing module.

For example, a library might define a protocol for "types that are downloaded from internet", or "types that are fetched from a database". Such a library may want to control when and where the conforming user types, and meta-types, are instantiated, or used. It is not unreasonable for a downloading library to decode user types off the main-actor, for example.

Now we have a problem. Client code, when MainActor-isolated by default, won't work by default:

// <<< MyLibrary.swift
public protocol LibraryProtocol: SendableMetaType { }

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is MainActor isolated.
// ❌ This will create compiler errors when the
// client module wants to use library features.
struct ClientType: LibraryProtocol { }

As far as I understand, in the current state of the language, client code must be written as below, with an explicit nonisolated modifier:

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is nonisolated, and library features can be used OK.
nonisolated struct ClientType: LibraryProtocol { }

This is not ergonomic for the library user, and this is an ergonomic disadvantage for the library. The library should be able to define the LibraryProtocol protocol in a way that makes this code work out of the box. That would be "approachable":

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is nonisolated, because LibraryProtocol
// requires so, and library features can be used OK.
struct ClientType: LibraryProtocol { }

Macros that inject nonisolated in the definition of the decorated type are not a suitable solution:

  • Conformance could not be added in an extension.
  • It would be unfair if only modules that define macros can force nonisolated in client code. This should not be a smarty-pants feature.
  • Too slow anyway
// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// Not a proper solution, IMHO.
@LibraryProtocol struct ClientType { }

To be exhaustive, the library should also be able to specify that an associated protocol is nonisolated:

// <<< MyLibrary.swift

// Should be able to enforce nonisolated in client code
public protocol LibraryProtocol: SendableMetaType {
    // Should be able to enforce nonisolated in client code
    associatedtype Buddy: SendableMetaType
}

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is nonisolated, because LibraryProtocol
// requires so, and library features can be used OK.
struct ClientType: LibraryProtocol {
    // ClientType.Buddy is nonisolated, because
    // LibraryProtocol.Buddy requires so,
    // and library features can be used OK.
    enum Buddy { ... }
}

Reproduction

// <<< MyLibrary.swift
public protocol LibraryProtocol: SendableMetaType { }

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is MainActor isolated.
// ❌ This will create compiler errors when the
// client module wants to use library features.
struct ClientType: LibraryProtocol { }

Expected behavior

// <<< MyLibrary.swift
// Protocol is declared with some extra qualifier,
// not shown here, that enforces nonisolated client types.
public protocol LibraryProtocol: SendableMetaType { }

// <<< ClientModule.swift (MainActor-isolated by default)
import MyLibrary

// ClientType is nonisolated, because LibraryProtocol
// requires so, and library features can be used OK.
struct ClientType: LibraryProtocol { }

A possible solution would be to repurpose nonisolated protocol introduced in SE-0449:

// nonisolated is enforced on confirming types
public nonisolated protocol LibraryProtocol: SendableMetaType { }

Or, if it looks like something more specific is needed:

// nonisolated is enforced on confirming types
public nonisolated(exported) protocol LibraryProtocol: SendableMetaType { }
public nonisolated(required) protocol LibraryProtocol: SendableMetaType { }

And for associated types:

public nonisolated protocol LibraryProtocol: SendableMetaType {
    // nonisolated is enforced on concrete types
    associatedtype Buddy: nonisolated SendableMetaType
}

Environment

Xcode Version 26.0 beta (17A5241e)

Additional information

I created this issue as a bug, not a feature request, considering how hostile it would be for libraries, and their users, if libraries would not be able to overtake default isolation in a way or another. Libraries should be trusted. User code should work out of the box, without non-approachable nonisolated struct XXX or using nonisolated incantations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.triage neededThis issue needs more specific labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions