@Douglas_Gregor has proposed an amendment to SE-0466 to smooth out a rough edge that was uncovered after acceptance of the original proposal. This change would disable @MainActor isolation inference for types that conform to a protocol inheriting from SendableMetatype.
I will be running an expedited review of this change from now until July 15, 2025.
What goes into a review?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:
What is your evaluation of the proposal?
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
More information about the Swift evolution process is available at:
Looks like the link that Steve provided currently points to the proposed amendments to SE-0470. Here's the corrected link to the SE-0466 amendment under review here:
Those of you who've liked the OP might want to take another gander at the corrected link and/or comment over on the review thread for amending SE-0470 once it is up
Hello! As a library maintainer, I'm massively in favor of this amendment.
I have two questions, just to make sure that the amendment enables what it is supposed to enable.
Do you confirm that @MainActor inference is disabled for types whose conformance to a SendableMetaType protocol is declared in the same file as the type declaration?
For example:
// Non-isolated, right?
enum CodingKeys1: CodingKey { ... }
// Non-isolated, right?
enum CodingKeys2 { ... }
extension CodingKeys2: CodingKey { ... }
// @MainActor-isolated, right? Even if CodingKeys3
// conforms to CodingKey in another file
enum CodingKeys3 { ... }
My second question Is about nested types. Do you confirm that all the Nested types below will be non-isolated?
In those files, conformances are declared on an extension, not right on the type declaration. Some users even conditionally conform to GRDB protocols with #if canImport(GRDB), which, well, is one more reason for splitting the type declaration and the conformance extension.
We already have multiple precedents in the language where same-file extensions are handled as if the conformance was included right on the type definition. Please extend this handling to the topic of this amendment.
If Player were still MainActor-isolated in the above sample code, well, those users would be rightfully disappointed..
My second question Is about nested types. Do you confirm that all the Nested types below will be non-isolated?
The impact of GRDB is the following. The Columns enum MUST be non-isolated so that the snippet in the "Usage" section is able to access name from the nonisolated closure executed by the read method:
<<< Player.swift
import GRDB
struct Player: Codable, Identifiable {
var id: Int64
var name: String
}
// MARK: - Database
extension Player: FetchableRecord, // can decode database rows
TableRecord // can generate SQL
{
enum Columns {
static let name = Column(CodingKeys.name)
}
}
// <<< OtherFile.swift
// Usage
let player = try await dbQueue.read { db in
// We're in a non-isolated closure here.
try Player.filter { $0.name == "Arthur" }.fetchOne(db)
}
Is the problem being addressed significant enough to warrant a change to Swift?
Yes, I tried user default actor isolation inference a couple days ago, and ended up not using it, because a lot of types where erroneously isolated, those where simple data structs and enums, conforming to Codable, Sendable and such typical value-type protocols.
Does this proposal fit well with the feel and direction of Swift?
Yes, sensible defaults
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
No
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
A quick reading, and quickly experimenting with a real, small project.
Since the review period of this amendment is about to finish, and the feedback was light, I'll make my position more clear:
I am against the current amendment as I understand it.
I wish the proposal would be clear and explicit about the isolation of nested types:
protocol P: SendableMetaType { }
// nonisolated
struct MyStruct: P {
// isolation of this nested type should be
// made explicit by the proposal.
struct Inner { }
}
The reason for this request is that the answer to this question is not obvious. And it's not a light one. Its consequences are quite impactful.
I wish nested types would not be more isolated than their parent, unless explicitly required:
The reason for this request is that if a nested type is more isolated than its parent type, then it becomes painful to use:
// nonisolated
struct MyStruct: P {
// If this type is @MainActorâŠ
struct Inner { }
// nonisolated
func myMethod() {
// ⊠then it can't be used in a nonisolated method đ
let inner = Inner()
}
}
I wish that a protocol that inherits SendableMetatype would suppress the default global-actor isolation when the conformance is declared in the same file as the definition of the conforming type:
A lot of prominent and influential Swift developers and coding styles favor and foster the use of extensions for declaring conformances. This is a very frequent practice, especially when the protocol has requirements. In this regard, the CodingKey protocol which is used as an example in the amendment is an exception. It is not CodingKey that should inform the design of the amendment.
If the language would ship without point 2. and 3. above, then introducing them later (due to the foreseeable feedback I have described) would be a breaking change.
We'd need an experimental/future language flag, an Xcode option, etc. It would be quite costly, not only for compiler engineers (not my direct concern, I agree), but also for library authors, who would have to write painfully precise documentation for the users of those libraries. Please remember that unlike the stdlib, third-party libraries frequently want to target several Swift versions.
That's why I insist on the problems created by their absence: they can be avoided, now.
I hope @Douglas_Gregor finds my requests sufficiently grounded.
I agree that it should be more explicit. Let's figure out the semantics:
I agree with the semantics you describe above, although I would say it differently: a nested type should have the isolation of its enclosing type unless one of the conformance-based inference rules (nonisolated due to a SendableMetatype-inheriting conformance, or a global actor due to a global-actor-isolated protocol) overrides it.
[EDIT: Captured this change in a pull request here]
The most direct precedent right now is inferring @MainActor from a protocol, which doesn't behave this way, e.g.,
@MainActor protocol Q { }
/*nonisolated*/ struct X { }
extension X: Q { } // does not cause X to be @MainActor
IIRC, the intuition here is that conformances declared as part of the primary type are core aspects of the type's identity vs. additional roles that it plays, so they can affect its isolation. It means that one doesn't have to look quite as far from the main definition of the type to find out what could influence it. It's a similar argument to, for example, not allowing instance properties to be defined in extensions: one would have to look much further to determine what's fundamentally part of the type's definition.
To your point, though...
I also prefer to put most conformances into extensions, but I think that's because many of them aren't intrinsically part of the type's identity. Sendable(Metatype)-inheriting protocols like CodingKey and Error, main-actor-isolated protocol like View and delegate protocols, etc. all feel like they are part of the identity of a type. I would have expected the FetchableRecord and TableRecord protocols you mentioned above to be similar.
I agree that we should follow the same model as global actor isolation inference from protocols. I think this provides an easier mental model because the language follows a consistent rule where the isolation on a protocol only influences the isolation of a conforming type when the conformance is written at the primary declaration.
The other cases in the language that have some behavior only when a conformance is written in the same source file as the primary declaration that I can think of are:
Conformance synthesis for Equatable, Hashable, etc
Checked conformances to Sendable
The above cases have specific motivation that doesn't apply to isolation inference from a protocol conformance. Conformance synthesis and checking conformances to Sendable must be able to see all stored properties of a type, which might not be accessible outside the source file of the primary declaration. These features also must support extensions, because the conformance might be conditional and you can't specify a conditional conformance at a primary type declaration. These same reasons don't apply to isolation inference; for example, we would not want to infer isolation on a generic type from a conditional protocol conformance, because that isolation would impact all specializations, not just the ones that conform to the protocol that has the actor isolation.
Thank you Doug and Holly for listening to my concerns
I'm happy that we come up with the same conclusion regarding the isolation of nested types. That's a big relief to me.
Our last divergence is about the same-file conformance.
You both make excellent points, and you make them quite clear. I'll try to change your minds again.
The most direct precedent right now is inferring @MainActor from a protocol, which doesn't behave this way, e.g.,
@MainActor protocol Q { }
/*nonisolated*/ struct X { }
extension X: Q { } // does not cause X to be @MainActor
This was the technique for declaring X as both nonisolated and conforming to Q.
Isn't SE-0449 the new way to achieve the same goal? Maybe we can start questioning older techniques.
Oh, thanks for pointing this out. I'll stop grounding my arguments on conditional conformances, or #if.
I do not feel at ease with this notion of "Identity". Extensions are losing ground, and I regret that. Isn't just "Identity" the return of the base class in disguise ? A surprising come back of OOP in a protocol-based language?
Surely the recent trend of macro-based APIs does not help. @Foo struct S {} softens earlier design decisions. Maybe language designers could resist a little bit, and keep on fostering composition over inheritance. We now have tools like SE-0449 in order to override isolation inference.
This "Identity" concept does not support the innocuous-looking refactoring that extracts a conformance to an extension, which puts users in a difficult situation. To understand, please follow along:
-struct S: P { ... }
+// Oh noes, you just lost type identity and the attached sugar.
+// App does no longer compile.
+struct S { ... }
+extension S: P { ... }
Users will have to compensate. Here, in the context of this thread, it's easy. We know what we are talking about. "They just have to add nonisolated! Not a big deal!"
nonisolated struct S { ... } // Extra nonisolated
extension S: P { ... }
OK. But this does not scale well. Not everybody knows exactly what attributes are implicitly added to a type by a protocol conformance. Actually, we should assume that the user has no clear knowledge of those implicit attributes, precisely because they are implicit. Losing the "identity" sugar is a difficult step.
SE-0466 is part of "approachable" concurrency, which, pardon my exaggerated summary, aims at removing as much concurrency-related decorations as possible in userland code. Within sensible limits, of course.
I still wish same-file extensions to a SendableMetaType protocol would disable MainActor isolation. Even if it might look like an exception, different circumstances can lead to different outcomes. It is not because an extension can not add an implicit global actor isolation that an extension can not remove one.
(I have not used GRDB as a justification for any of my arguments here - I'm not asking for a favor for a particular library. That library was just the context that helped me spotting future user issues that will impact other libraries as well).
Isolation as implicit state (akin to self), and having a modifiable default of that, does seem to require close association with the storage aspect of a type, and there doesn't seem to be a convenient precedent for saying that other than the defining type conformance clause.
But conforming to protocols pretty much describes the design space for application programmers participating in OS and library services. Scattering the "what" and "how" of those extension declarations destroys source-level modularity and adds source and cognitive friction to each use.
Also, compiler-control conditions are a primary mechanism for compile-time dispatch to (a) deploy swift to all its highly complicated platforms (swift/compiler version, arch, os+version, test/prod) and (b) explore API, runtime, and design variants -- any one of which might require an inference-disabling protocol.
Currently it's not permitted to declare a conformance to a protocol twice. Permitting that in this case (via detection of SendableMetaType?) would at least restore the coherence of the extension conformance, but the user would still need to edit the primary declaration to make any changes.
For that, I wonder if an empty extension-conformance declaration could be treated in the same way as a declaration on the primary type (at least when in the same file or module as the primary type?), and avoid the need to edit the type when writing conditional code.
Assuming SendableMetaType protocols GRDB.D and Std.S, the user would write code like:
struct M {...}
#if canImport(GRDB)
extension M: GRDB.D {}
...
extension M { // : D if dups allowed here
// D implementation
}
#else
extension M: Std.S {}
extension M { // : P if dups allowed here
// S implementation
}
#endif
If indeed there is a special category that includes CodingKey and Error (I think of it more like storage definitiveness than identity), this makes it somewhat teachable to have a qualified exception to the general rule with well-known examples.
Also these data serialization and error types are good candidates for macros and generated code, making some source-understandable and source-severable locution even more helpful.
(I wonder if any of the various means of applying conformances via macros run afoul of any requirement to be declared on the defining type?)
I think an explicit conformance to Sendable strongly carries the meaning, if not the intention, of non-isolated. So strongly that it should also be inferred from same-file-extension.
I think conditional conformance also carries that meaning. In my experience, conditional conformance to Sendable is a convenience to allow a non-Sendable payload inside an otherwise Sendable type.
On my experience Sendable-constrained protocols also carry that meaning, but Iâm ready to hear otherwise. And with the above we would have the option of explicit conformance to Sendable to infer non-isolated, which in my opinion is more meaningful than explicit non-isolated alone.
And finally I think itâs relevant to have a slightly different rule between inference because of Sendable and inference because of isolation, if it means more consistency between checked conformance to Sendable and isolation inference from Sendable.
Can you address the question about macros? It seems appropriate to infer actor isolation based off an extension macro since the macro is attached directly to the type and would fall in line with the type's "identity," especially since we don't have a "conformance" macro that can add a conformance directly to the type declaration.
For example, if @Fooable adds a sendable Foo conformance to Bar, it's unfortunate that this can't compile:
@Fooable
struct Bar {
// ...
}
Instead, users of these APIs must explicitly mark these structs nonisolated even though it is technically inferable (and the errors will be cryptic and hidden in the macro expansion).
This isn't great for a few reasons. First, this parameter will need to be added to lots of macros out there, which seems repetitive and hacky. And second, the user may not know they need to provide the argument, and they will get cryptic error messages that can't let them know the fix is to add a parameter to the macro.
Are there any other solutions to this problem right now or planned?
To the proposal author: I've already said that, but yes the language design should help libraries removing the default MainActor isolation of user types, when that isolation is incompatible with the library API.
I can see myself write extra documentation, in an attempt to guide the users to a working solution. But this documentation has to depend on the default isolation, and is a little bit complicated:
If your application or library uses the default MainActor isolation (.defaultIsolation(MainActor.self) in your package, or SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor in Xcode target settings), then take care to declare them as nonisolated, as below:
[sample code]
Reading and understanding this extra documentation, and applying the recommended practice, would be a mandatory step for users who create a new project, which defaults to MainActor isolation in the future Xcode 26 release.
Let's talk about user experience, a little bit: the naive way of discovering a library (create project, add dependency, dive in after a quick glance at the documentation, maybe some ChatGPT boilerplate) leads to compilation failures. This is not a good onboarding experience. If we are lucky, the user does not immediately discards the library, and opens an issue or a discussion. We'll answer that there is no issue and that an extra nonisolated is required, because, you know, the language design.
We library maintainers do deserve a much better support from the language, whether we define macros, or not.
Please amend the amendment, now, before Swift 6.2 ships and backward compatibility concerns makes everything even more complicated.
And I'll also raise again my concerns about that "identity" concept mentionned here. It has all the appearances of an innocent-looking post-hoc patchy concept that would introduce an important and undesired (IMHO) shift to the language.
In general, it is a good idea to keep the main struct/class definition as short as possible: preferably it should consist of the type's stored properties and a handful of critical initializers, and nothing else.
Everything else should go in standalone extensions, arranged by logical theme. For example, it's often nice to define protocol conformances in dedicated extensions (emphasis mine). [...]
Again, I plea for being very cautious with an "identity" concept that has an effect on compilation. It is most likely a weak justification for a design defect in the amendment under review, and goes against years of language direction. Such a wide-ranging shift has no place in the review of a proposal amendment anyway.
Post-scriptum: as a library developer, I put all the rationality I can in my critics here, but in reality I'm freaked out by the consequences of a badly patched SE-0466. No wonder multiple developers have asked for an exception ("pleaaaaase not us!!!!"). But we do not need exceptions. We need a fixed SE-0466.
The review was supposed to end on July 15, 2025. I know it's August and maybe people are in vacations. It would be nice to have some feedback on the multiple concerns that have been expressed above (I'm not alone expressing some). I would regret to see this case closed too hastily!
Just wanted to follow up on a more positive note here. While default main actor isolation impacted a bunch of our libraries that optimized for data races by adopting sendability more places, weâve found that relaxing sendability requirements have improved things and we can support both nonisolated and main actor isolated use cases without much trouble, and in fact weâve been able to make improvements thanks to isolated conformances. (Our only worry is a few soundness holes weâve found, but hopefully theyâll be plugged soon!)