From 5e5349ef02ca5eddf04473570c417b83a6e8a908 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Sat, 7 Jun 2025 11:07:25 -0500 Subject: [PATCH 1/7] Add discussion of global actor isolation inference. [SE-0316] This change adds documentation for isolation inference from class inheritance, overridden methods, and protocol conformances. --- TSPL.docc/LanguageGuide/Concurrency.md | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/TSPL.docc/LanguageGuide/Concurrency.md b/TSPL.docc/LanguageGuide/Concurrency.md index de0a3ed89..81f182ae8 100644 --- a/TSPL.docc/LanguageGuide/Concurrency.md +++ b/TSPL.docc/LanguageGuide/Concurrency.md @@ -1392,6 +1392,118 @@ You can define your own singleton global actors using the `@globalActor` attribute, as described in . +### Isolation inference + +Global actor isolation can be inferred in the code you write. Isolation +is inferred from class inheritance, protocol conformances, and context +where code is written. + +#### Classes + +If a superclass is isolated to a global actor, the global actor is +inferred on subclasses. For example, the code below has a main-actor +isolated class `Vehicle`, and a subclass `Train` that inherits +main-actor isolation: + +```swift +@MainActor +class Vehicle { + var currentSpeed = 0.0 + func makeNoise() { + // do nothing - an arbitrary vehicle doesn't necessarily make a noise + } +} + +class Train: Vehicle { + override func makeNoise() { + print("Choo Choo") + } +} +``` + +In the above example, `Vehicle` and all of its methods and properties +are isolated to the main actor. The `Train` subclass infers its isolation +from the superclass, so the overridden `makeNoise` method is also isolated +to the main actor. + +Global actor isolation is also inferred from individual overridden +methods. For example, the following code isolates one method of the +`Vehicle` class to the main actor instead of the entire class: + +```swift +class Vehicle { + var currentSpeed = 0.0 + + @MainActor + func makeNoise() { + // do nothing - an arbitrary vehicle doesn't necessarily make a noise + } +} + +class Train: Vehicle { + override func makeNoise() { + print("Choo Choo") + } +} +``` + +The override of `makeNoise` in `Train` infers main-actor isolation from +the overridden `makeNoise` method in `Vehicle`. + +#### Protocols + +If a protocol is isolated to a global actor, the global actor is +inferred on conforming types. For example, the following code has +a main-actor isolated protocol `Togglable`, and a conforming struct +`Switch`: + +```swift +@MainActor +protocol Togglable { + mutating func toggle() +} + +struct Switch: Togglable { + var isOn: Bool = false + + mutating func toggle() { + isOn.toggle() + } +} +``` + +In the above example, `Togglable` and all of its requirements +are isolated to the main actor. The `Switch` type infers its isolation +from the protocol conformance, and the implementation of `toggle` is +isolated to the main actor. + +Isolation is only inferred from protocols when the conformance is +written at the primary declaration. If the conformance is written in +an extension, then isolation inference only applies to requirements that +are implemented in the extension. For example, the following code +implements a conformance of `Switch` to `Togglable` in an extension: + +```swift +@MainActor +protocol Togglable { + mutating func toggle() +} + +struct Switch: Togglable { + var isOn: Bool = false +} + +extension Switch: Togglable { + mutating func toggle() { + isOn.toggle() + } +} +``` + +Now, the `Switch` type itself does not have inferred isolation. The +`toggle` method inside `Switch` is isolated to the main actor, because +the protocol requirement that it implements is isolated to the main actor. + -If a superclass has global actor isolation, Swift infers that global actor -on subclasses. For example, the code below has a main-actor -isolated class `Vehicle`, and a subclass `Train` that inherits -from `Vehicle`: +For classes that are isolated to a global actor, +Swift infers their subclasses have the same isolation. +For example, +the code below declares a main-actor isolated class `Vehicle`, +and a subclass `Train` that inherits from `Vehicle`: ```swift @MainActor @@ -1414,20 +1414,30 @@ class Vehicle { } class Train: Vehicle { + func announceDeparture() { + print("All aboard!") + } override func makeNoise() { print("Choo Choo") } } ``` -In the above example, all methods and properties in `Vehicle` -are isolated to the main actor. The `Train` class inherits all -methods, properties, and global actor isolation from the `Vehicle` -superclass, so Swift infers main-actor isolation for the `makeNoise` -override. - -Swift also infers global-actor isolation from individual overridden -methods. For example, the following code isolates one method of the +In the example above, +the `Vehicle` class is isolated to the main actor --- +writing `@MainActor` on the type +isolates all of its methods and properties to the main actor. +The `Train` subclass of `Vehicle` inherits this main-actor isolation, +which isolates all of the following to the main actor: + +- Methods and properties it inherits, such as `currentSpeed`. +- Methods and properties it adds, such as `announceDeparture()`. +- Methods and properties in overrides, such as `makeNoise()`. + +When subclassing a type that isn't isolated to a global actor, +Swift infers that overrides to any global-actor methods +are also isolated to that global actor. +For example, the following code isolates one method of the `Vehicle` class to the main actor instead of the entire class: ```swift @@ -1447,17 +1457,23 @@ class Train: Vehicle { } ``` -Swift infers main-actor isolation for the `makeNoise` override of -the `Train` subclass based on the isolation of the `makeNoise` method -in `Vehicle`. - -#### Protocols - -Swift infers global-actor isolation from protocol conformances. -When a type conforms to a protocol, Swift infers actor isolation from -the protocol itself, and from individual protocol requirements. -For example, the following code has a main-actor isolated protocol -`Togglable`, and a conforming struct `Switch`: +Because the `makeNoise()` method of `Vehicle` is marked `@MainActor`, +Swift infers that an override in a subclass +is also isolated to the main actor. +In the code above, the `makeNoise()` method of `Train` +isn't explicitly marked `@MainActor` +but its main-actor isolation is inferred +from the method on `Vehicle` that it overrides. + +In addition to the places shown above +where Swift infers isolation from a superclass, +Swift also infers isolation from protocol conformances. +When a type conforms to a protocol, +Swift infers isolation from the protocol itself, +and from individual protocol requirements. +For example, +the following code has a main-actor isolated protocol `Togglable`, +and a conforming struct `Switch`: ```swift @MainActor @@ -1474,16 +1490,19 @@ struct Switch: Togglable { } ``` -In the above example, `Togglable` and all of its requirements -are isolated to the main actor. Swift infers main-actor isolation -on types that conform to `Togglable`, so all methods and properties -of `Switch` are isolated to the main actor, including the `isOn` -property and the `toggle` method. - -Swift only infers isolation from protocols when you write the conformance -at the primary declaration. If you write the conformance in -an extension, then isolation inference only applies to requirements that -are implemented in the extension. For example, the following code +In the example above, +the `Togglable` protocol is marked `@MainActor` +to indicate that all of its requirements are isolated to the main actor. +Swift infers main-actor isolation on types that conform to `Togglable`, +so all methods and properties of `Switch` are isolated to the main actor, +including the `isOn` property and the `toggle` method. + +Swift infers isolation from protocols +only when you write the conformance as part of the type's declaration. +If you write the conformance in an extension, +then isolation inference applies to +only requirements that are part of that the extension. +For example, the following code implements a conformance of `Switch` to `Togglable` in an extension: ```swift @@ -1492,7 +1511,7 @@ protocol Togglable { mutating func toggle() } -struct Switch: Togglable { +struct Switch { var isOn: Bool = false } @@ -1503,13 +1522,22 @@ extension Switch: Togglable { } ``` -Swift does not infer global-actor isolation on the `Switch` type itself; -the `Switch` type is `nonisolated`, and the methods and properties directly -inside the type are `nonisolated`. Swift infers global-actor isolation for -the protocol requirements implemented in the extension that declares the -conformance to `Togglable`, so the `toggle` method is isolated to the -main actor. + +Because the declaration of `Switch` +doesn't include conformance to the `Togglable` protocol, +`Switch` is understood as `nonisolated`, +and the methods and properties declared inside it are also `nonisolated`. +However, +Swift infers main-actor isolation +for the extension because it implements `Togglable`, +which is a main-actor-isolated protocol. +This inference means the `toggle()` method is isolated to the main actor. For classes that are isolated to a global actor, -Swift infers their subclasses have the same isolation. +Swift infers that their subclasses have the same isolation. For example, the code below declares a main-actor isolated class `Vehicle`, and a subclass `Train` that inherits from `Vehicle`: From 684162701b9c3262c6d384e4f7ac083af6cd4479 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 15 Sep 2025 14:58:40 -0700 Subject: [PATCH 5/7] Remove stale TR query --- TSPL.docc/LanguageGuide/Concurrency.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/TSPL.docc/LanguageGuide/Concurrency.md b/TSPL.docc/LanguageGuide/Concurrency.md index c973ed7b5..16199d1d6 100644 --- a/TSPL.docc/LanguageGuide/Concurrency.md +++ b/TSPL.docc/LanguageGuide/Concurrency.md @@ -1522,13 +1522,6 @@ extension Switch: Togglable { } ``` - - Because the declaration of `Switch` doesn't include conformance to the `Togglable` protocol, `Switch` is understood as `nonisolated`, From 0d7cf4a82b3856e8ac74f6b87f21f31f1f76196e Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Thu, 25 Sep 2025 15:59:10 -0700 Subject: [PATCH 6/7] Add a section intro --- TSPL.docc/LanguageGuide/Concurrency.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TSPL.docc/LanguageGuide/Concurrency.md b/TSPL.docc/LanguageGuide/Concurrency.md index 16199d1d6..5945904eb 100644 --- a/TSPL.docc/LanguageGuide/Concurrency.md +++ b/TSPL.docc/LanguageGuide/Concurrency.md @@ -1393,10 +1393,10 @@ as described in . ## Isolation Inference - +When you subclass an actor-isolated class +and conform to an actor-isolated protocol, +Swift infers the actor isolation for you. +These inference rules apply to the main actor and to other global actors. For classes that are isolated to a global actor, Swift infers that their subclasses have the same isolation. From 481f72ecbd5f4551ce3008b5153dcb1ae9524a72 Mon Sep 17 00:00:00 2001 From: Alex Martini Date: Mon, 29 Sep 2025 16:27:54 -0700 Subject: [PATCH 7/7] Apply edits from Co-authored-by: Colleen Toporek --- TSPL.docc/LanguageGuide/Concurrency.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/TSPL.docc/LanguageGuide/Concurrency.md b/TSPL.docc/LanguageGuide/Concurrency.md index 5945904eb..a9b9c4d1e 100644 --- a/TSPL.docc/LanguageGuide/Concurrency.md +++ b/TSPL.docc/LanguageGuide/Concurrency.md @@ -1374,12 +1374,13 @@ you'll get compile-time error instead of introducing a bug. ## Global Actors The main actor is a global singleton instance of the [`MainActor`][] type. -An actor can normally have multiple instances, +Normally, an actor can have multiple instances, each of which provides independent isolation. -This is why you declare all of an actor's isolated data +The possibility of multiple instances +is why you declare all of an actor's isolated data as instance properties of that actor. -However, because `MainActor` is singleton --- -there is only ever a single instance of this type --- +However, because `MainActor` is a singleton --- +there's only ever a single instance of this type --- the type alone is sufficient to identify the actor, allowing you to mark main-actor isolation using just an attribute. This approach gives you more flexibility to organize your code @@ -1409,7 +1410,7 @@ and a subclass `Train` that inherits from `Vehicle`: class Vehicle { var currentSpeed = 0.0 func makeNoise() { - // do nothing - an arbitrary vehicle doesn't necessarily make a noise + // Do nothing: an arbitrary vehicle doesn't necessarily make a noise. } } @@ -1446,7 +1447,7 @@ class Vehicle { @MainActor func makeNoise() { - // do nothing - an arbitrary vehicle doesn't necessarily make a noise + // Do nothing: an arbitrary vehicle doesn't necessarily make a noise. } } @@ -1497,11 +1498,11 @@ Swift infers main-actor isolation on types that conform to `Togglable`, so all methods and properties of `Switch` are isolated to the main actor, including the `isOn` property and the `toggle` method. -Swift infers isolation from protocols -only when you write the conformance as part of the type's declaration. +Swift only infers isolation from protocols +when you write the conformance as part of the type's declaration. If you write the conformance in an extension, -then isolation inference applies to -only requirements that are part of that the extension. +then isolation inference only applies to +requirements that are part of that extension. For example, the following code implements a conformance of `Switch` to `Togglable` in an extension: