Skip to content

Commit c1a42d4

Browse files
authored
[SE-0466] Disable @MainActor inference when type conforms to a SendableMetatype protocol (#2894)
* [SE-0466] Disable `@MainActor` inference when type conforms to a SendableMetatype protocol Libraries need a way to communicate that certain protocols can't reasonably be used with global-actor-isolated types. When the primary definition of a type conforms to such a protocol, it will disable `@MainActor` inference for that type. This has the effect of keeping more types `nonisolated` when there's signal that they should not be on the main actor. There are some protocols like this in the standard library (e.g., CodingKey, which was hardcoded to be nonisolated in the synthesis code), where the following code is ill-formed in with main-actor isolation by default: struct S: Codable { var a: Int // error: CodingKeys inferred to `@MainActor`, which makes the conformance // to CodingKey main-actor-isolated and then fails (because the requirements // need to be nonisolated). enum CodingKeys: CodingKey { case a } } With this amendment, the conformance to `CodingKey` (which inherits from `Sendable`), prevents `CodingKeys` from being inferred to be`@MainActor`. * Provide more extensive rationale for the design
1 parent 816d883 commit c1a42d4

File tree

1 file changed

+34
-9
lines changed

1 file changed

+34
-9
lines changed

proposals/0466-control-default-actor-isolation.md

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ When the default actor isolation is specified as `MainActor`, declarations are i
6565
* Declarations with inferred actor isolation from a superclass, overridden method, protocol conformance, or member propagation
6666
* All declarations inside an `actor` type, including static variables, methods, initializers, and deinitializers
6767
* Declarations that cannot have global actor isolation, including typealiases, import statements, enum cases, and individual accessors
68+
* Declarations whose primary definition directly conforms to a protocol that inherits `SendableMetatype`
6869

6970
The following code example shows the inferred actor isolation in comments given the code is built with `-default-isolation MainActor`:
7071

@@ -107,6 +108,16 @@ struct S: P {
107108
// @MyActor
108109
func f() { ... }
109110
}
111+
112+
nonisolated protocol Q: Sendable { }
113+
114+
// nonisolated
115+
struct S2: Q { }
116+
117+
// @MainActor
118+
struct S3 { }
119+
120+
extension S3: Q { }
110121
```
111122

112123
This proposal does not change the default isolation inference rules for closures. Non-Sendable closures and closures passed to `Task.init` already have the same isolation as the enclosing context by default. When specifying `MainActor` isolation by default in a module, non-`@Sendable` closures and `Task.init` closures will have inferred `@MainActor` isolation when the default `@MainActor` inference rules apply to the enclosing context:
@@ -166,23 +177,37 @@ Similarly, a future language mode could enable main actor isolation by default,
166177

167178
See the approachable data-race safety vision document for an [analysis on the risks of introducing a language dialect](https://github.com/hborla/swift-evolution/blob/approachable-concurrency-vision/visions/approachable-concurrency.md#risks-of-a-language-dialect) for default actor isolation.
168179

169-
### Don't apply default actor isolation to explicitly `Sendable` types
180+
### Alternative to `SendableMetatype` for suppressing main-actor inference
181+
182+
The protocols to which a type conforms can affect the isolation of the type. Conforming to a global-actor-isolated protocol can infer global-actor isolatation for the type. When the default actor isolation is `MainActor`, it is valuable for protocols to be able to push inference toward keeping conforming types `nonisolated`, for example because conforming types are meant to be usable from any isolation domain.
170183

171-
This proposal includes few exceptions where the specified default actor isolation does not apply. An additional case that should be considered is types with a conformance to `Sendable`:
184+
In this proposal, inheritance from `SendableMetatype` (introduced in [SE-0470](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0470-isolated-conformances.md)) is used as an indication that types conforming to the protocol should be `nonisolated`. The `SendableMetatype` marker protocol indicates when a type (but not necessarily its instances) can cross isolation domains, which implies that the type generally needs to be usable from any isolation domain. Additionally, protocols that inherit from `SendableMetatype` can only be meaningfully be used with nonisolated conformances, as discussed in SE-0470. Experience using default main actor isolation uncovered a number of existing protocols that reinforce the notion of `SendableMetatype` inheritance is a reasonable heuristic to indicate that a conforming type should be nonisolated: the standard library's [`CodingKey`](https://developer.apple.com/documentation/swift/codingkey) protocol inherits `Sendable` (which in turn inherits `SendableMetatype`) so a typical conformance will fail to compile with default main actor isolation:
172185

173186
```swift
174-
struct SimpleValue: Sendable {
175-
var value: Int
187+
struct S: Codable {
188+
var a: Int
189+
190+
// error if CodingKeys is inferred to `@MainActor`. The conformance cannot be main-actor-isolated, and
191+
// the requirements of the (nonisolated) CodingKey cannot be satisfied by main-actor-isolated members of
192+
// CodingKeys.
193+
enum CodingKeys: CodingKey {
194+
case a
195+
}
176196
}
177197
```
178198

179-
This is an attractive carve out upon first glance, but there are a number of downsides:
199+
Other places that have similar issues with default main actor isolation include the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable) protocol and the uses of key paths in the [`@Model` macro](https://developer.apple.com/documentation/swiftdata/model()).
200+
201+
Instead of using `SendableMetatype` inheritance, this proposal could introduce new syntax for a protocol to explicitly indicate
180202

181-
* The carve out may be confusing if a conformance to `Sendable` is implied, e.g. through a conformance to another protocol.
182-
* Global actor isolation implies a conformance to `Sendable`, so it's not clear that a `Sendable` type should not be global actor isolated.
183-
* Methods on a `Sendable` type may still use other types and methods that have default actor isolation applied, which would lead to failures if the `Sendable` type was exempt from default isolation inference.
203+
```swift
204+
@nonisolatedConformingTypes
205+
public protocol CodingKey {
206+
// ...
207+
}
208+
```
184209

185-
A middle ground might be to not apply default actor isolation to types with an explicit conformance to `Sendable` within the same source file as the type. This approach would still have some of the downsides listed above, but it would be more straightforward to spot types that have this exemption.
210+
This would make the behavior pushing conforming types toward `nonisolated` opt-in. However, it means that existing protocols (such as the ones mentioned above) would all need to adopt this spelling before code using default main actor isolation will work well. Given the strong semantic link between `SendableMetatype` and `nonisolated` conformances and types, the proposed rule based on `SendableMetatype` inheritance is likely to make more code work well with default main actor isolation. An explicit opt-in attribute like the above could be added at a later time if needed.
186211

187212
### Use an enum for the package manifest API
188213

0 commit comments

Comments
 (0)