Skip to content

Add discussion of global actor isolation inference. [SE-0316] #371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 147 additions & 1 deletion TSPL.docc/LanguageGuide/Concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,6 @@ if you try to add concurrent code to this function,
introducing a possible suspension point,
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.
Expand All @@ -1392,6 +1391,153 @@ You can define your own singleton global actors
using the `@globalActor` attribute,
as described in <doc:Attributes#globalActor>.

## Isolation Inference

<!-- XXX introduction
- it would be tedious (or introduce errors) to not have these rules
- these inference rules apply to the main actor and other global actors
-->

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
class Vehicle {
var currentSpeed = 0.0
func makeNoise() {
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
}
}

class Train: Vehicle {
func announceDeparture() {
print("All aboard!")
}
override func makeNoise() {
print("Choo Choo")
}
}
```

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
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")
}
}
```

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
protocol Togglable {
mutating func toggle()
}

struct Switch: Togglable {
var isOn: Bool = false

mutating func toggle() {
isOn.toggle()
}
}
```

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
@MainActor
protocol Togglable {
mutating func toggle()
}

struct Switch {
var isOn: Bool = false
}

extension Switch: Togglable {
mutating func toggle() {
isOn.toggle()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this example the function toggle is isolated to the main actor but isOn is not isolated to the main actor, is it? And if that’s the case, isn’t it not allowed to use it here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this example, isOn is nonisolated, which is fine to access from a main actor isolated method.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, is that because you can only access the toggle function if your Switch is currently living in the main actor and thus any access to isOn would necessarily be on the main actor as well? I always forget about that. Very cool.

}
}
```

<!-- XXX TR:
Was it intentional that Switch declaration listed conformance to Togglable
without implementing the protocol requirement? When I build the code with
that, I get an error about redundant conformance in the extension, and it
didn't quite match the prose, so I removed it.
-->

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.

<!--
OUTLINE
Expand Down