You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[SE-0466: Control default actor isolation inference][SE-0466] introduced the ability to specify default actor isolation on a per-module basis. This proposal introduces a new typealias for specifying default actor isolation in individual source files within a module. This allows specific files to opt out of main actor isolation within a main-actor-by-default module, and opt into main actor isolation within a nonisolated-by-default module.
15
+
[SE-0466: Control default actor isolation inference][SE-0466] introduced the ability to specify default actor isolation on a per-module basis. This proposal introduces a new declaration for specifying default actor isolation in individual source files within a module. This allows specific files to opt out of main actor isolation within a main-actor-by-default module, and opt into main actor isolation within a nonisolated-by-default module.
16
16
17
17
## Motivation
18
18
19
19
SE-0466 allows code to opt in to being “single-threaded” by default by isolating everything in the module to the main actor. When the programmer really wants concurrency, they can request it explicitly by marking a function or type as `nonisolated`, or they can define it in a module that does not default to main-actor isolation. However, it's very common to group multiple declarations used in concurrent code into one source file or a small set of source files. Instead of choosing between writing `nonisolated` on each individual declaration or splitting those files into a separate module, it's desirable to state that all declarations in those files default to `nonisolated`.
20
20
21
21
## Proposed solution
22
22
23
-
This proposal allows writing a private typealias named `DefaultIsolation` to specify the default actor isolation for a file.
24
-
25
-
An underlying type of `MainActor` specifies that all declarations in the file default to main actor isolated:
23
+
This proposal allows writing a new kind of declaration to specify the default actor isolation for a file. The isolation is specified with the `using` keyword, followed by the default isolation for the file. Writing `using @MainActor` specifies that all declarations in the file default to main actor isolated:
26
24
27
25
```swift
28
26
// main.swift
29
27
30
-
privatetypealiasDefaultIsolation=MainActor
28
+
using @MainActor
31
29
32
30
// Implicitly '@MainActor'
33
31
var global =0
@@ -38,12 +36,12 @@ func main() { ... }
38
36
main()
39
37
```
40
38
41
-
An underlying type of `nonisolated` specifies that all declarations in the file default to `nonisolated`:
39
+
Writing `using nonisolated` specifies that all declarations in the file default to `nonisolated`:
42
40
43
41
```swift
44
42
// Point.swift
45
43
46
-
privatetypealiasDefaultIsolation= nonisolated
44
+
usingnonisolated
47
45
48
46
// Implicitly 'nonisolated'
49
47
structPoint {
@@ -54,34 +52,71 @@ struct Point {
54
52
55
53
## Detailed design
56
54
57
-
A typealias named `DefaultIsolation` can specify the actor isolation to use for the source file it's written in under the following conditions:
55
+
The following production rules describe the grammar of `using` declarations:
* The typealias is `private` or `fileprivate`; the `DefaultIsolation` typealias cannot be used to set the default isolation for the entire module, so its access level cannot be `internal` or above.
61
-
* The underlying type is either `MainActor` or `nonisolated`.
62
+
The `using` keyword can be followed by an attribute, a declaration modifier, or a call expression. This proposal only supports `using @MainActor` and `using nonisolated`; any other attribute, modifier, or expression written after `using` is an error. The general grammar rules allow `using` to be expanded in the future.
62
63
63
-
It is not invalid to write a typealias called `DefaultIsolation` that does not meet the above conditions. Any typealias named `DefaultIsolation` that does not meet the above conditions will be skipped when looking up the default isolation for the source file. The compiler will emit a warning for any `DefaultIsolation` typealias that is not considered for default actor isolation along with the reason why:
64
+
Writing a `using` declaration is only valid at the top-level scope; it is an error to write `using` in any other scope:
64
65
65
66
```swift
66
-
@globalActor
67
-
actorCustomGlobalActor {
68
-
staticlet shared =CustomGlobalActor()
67
+
funcf() {
68
+
using @MainActor// error
69
69
}
70
+
```
71
+
72
+
Specifying `using @MainActor` anywhere in the file will instruct the compiler to use `@MainActor` as the default isolation for unspecified declarations:
70
73
71
-
privatetypealiasDefaultIsolation= CustomGlobalActor // warning: not used for default actor isolation
74
+
```swift
75
+
using @MainActor
76
+
77
+
// Implicitly '@MainActor'
78
+
structS {}
79
+
80
+
// still 'nonisolated'
81
+
nonisolatedstructT {}
72
82
```
73
83
74
-
To allow writing `nonisolated` as the underlying type of a typealias, this proposal adds a typealias named `nonisolated` to the Concurrency library:
84
+
85
+
Specifying `using nonisolated` anywhere in the file will instruct the compiler to use `nonisolated` as the default isolation for unspecified declarations:
75
86
76
87
```swift
77
-
publictypealiasnonisolated=Never
88
+
using nonisolated
89
+
90
+
// Implicitly 'nonisolated'
91
+
structS {}
92
+
93
+
// still '@MainActor'
94
+
@MainActorstructT {}
78
95
```
79
96
80
-
This typealias serves no purpose beyond specifying default actor isolation. To specify `nonisolated` using the `DefaultIsolation` typealias, the underlying type must be `nonisolated` exactly; it is invalid to write `private typealias DefaultIsolation = Never`.
97
+
`using` follows the same isolation inference rules as SE-0466. An isolation specified by `using` is only used as a default, meaning that all other isolation inference rules from an explicit annotation on a declaration are preferred. For example, inference from a protocol conformance is preferred over default actor isolation:
98
+
99
+
```swift
100
+
// In MyLibrary
101
+
102
+
@MainActor
103
+
protocolP {}
104
+
105
+
// In MyClient
106
+
importMyLibrary
107
+
108
+
using nonisolated
109
+
110
+
// '@MainActor' inferred from 'P'
111
+
structS: P {}
112
+
113
+
// Implicitly 'nonisolated'
114
+
funcf() {}
115
+
```
81
116
82
117
## Source compatibility
83
118
84
-
Technically source breaking if someone happens to have written a private `DefaultIsolation` typealias with an underlying type of `MainActor`, which will start to infer every declaration in that file as `@MainActor`-isolated after this change. This seems extremely unlikely.
119
+
This is an additive feature with no impact on existing code.
85
120
86
121
## ABI compatibility
87
122
@@ -93,23 +128,52 @@ This proposal does not change the adoption implications of adding `@MainActor` t
93
128
94
129
## Alternatives considered
95
130
96
-
Adding a typealias named `nonisolated`to `Never` to the Concurrency library to enable writing it as the underlying type of a typealias is pretty strange; this approach leverages the fact that `nonisolated` is a contextual keyword, so it's valid to use `nonisolated` as an identifier. This proposal uses a typealias instead of an empty struct or enum type to avoid the complications of having a new type be only available with the Swift 6.2 standard library.
131
+
### A typealias to specify default isolation per file
97
132
98
-
It's extremely valuable to have a consistent way to spell `nonisolated`. Introducing a type that follows standard naming conventions, such as `Nonisolated`, or using an existing type like `Never` is more consistent with recommended style, but overall complicates the concurrency model because it means you need to spell `nonisolated` differently when specifying it per file versus writing it on a declaration. And because the underlying type of this typealias is used to infer actor isolation, it's not used as a type in the same way that other typealiases are.
133
+
A previous iteration of this proposal used a typealias to specify default actor isolation instead of a new syntax:
99
134
100
-
Another alternative is to introduce a bespoke syntax such as `using MainActor` or `using nonisolated`. This approach preserves a consistent spelling for `nonisolated`, but at the cost of adding new language syntax that deviates from other defaulting rules such as the default literal types and the default actor system types.
135
+
```swift
136
+
// main.swift
101
137
102
-
Having a `nonisolated`typealias may also allow us to improve the package manifest APIs for specifying default isolation, allowing us to move away from using `nil` to specify `nonisolated`:
138
+
privatetypealiasDefaultIsolation= MainActor
103
139
104
-
```swift
105
-
SwiftSetting.defaultIsolation(nonisolated.self)
140
+
// Implicitly '@MainActor'
141
+
var global =0
142
+
143
+
// Implicitly '@MainActor'
144
+
funcmain() { ... }
145
+
146
+
main()
106
147
```
107
148
108
-
We can also pursue allowing bare metatypes without `.self` to allow:
149
+
Though the typealias model is consistent with other language defaulting rules such as default literal types, there are a number of serious downsides:
150
+
151
+
1. The typealias must be `private` or `fileprivate` to limit its scope to the current file.
152
+
2. The right hand side of the typealias is conceptually not a type, because it must be able to represent `nonisolated`, and the proposal added a typelias of `nonisolated` to `Never` to enable writing `nonisolated` as the underlying type.
153
+
3. The typealias name, `DefaultIsolation`, serves no purpose beyond affecting the compiler's inference. The name `DefaultIsolation` cannot be used explicitly on an individual declaration to impact its isolation.
154
+
155
+
Adding a typealias named `nonisolated` to `Never` to the Concurrency library to enable writing it as the underlying type of a typealias is pretty strange; this approach leverages the fact that `nonisolated` is a contextual keyword, so it's valid to use `nonisolated` as an identifier. Using an empty struct or enum type would introduce complications of having a new type be only available with the Swift 6.2 standard library. All of these solutions allow `nonisolated` to be written in contexts where it should never appear, such as parameter and result types of functions.
156
+
157
+
It's extremely valuable to have a consistent way to spell `nonisolated`. Introducing a type that follows standard naming conventions, such as `Nonisolated`, or using an existing type like `Never` is more consistent with recommended style, but overall complicates the concurrency model because it means you need to spell `nonisolated` differently when specifying it per file versus writing it on a declaration. And because the underlying type of this typealias is used to infer actor isolation, it's not used as a type in the same way that other typealiases are.
158
+
159
+
A bespoke syntax, as included in this proposal iteration, solves all of the above problems. The cost is adding new language syntax that deviates from other defaulting rules, but the benefits outweigh the costs:
160
+
161
+
1.`using` doesn't include extra ceremony like `private` or `fileprivate`.
162
+
2. It preserves a consistent way of spelling `nonisolated` without allowing `nonisolated` to be written in places where it shouldn't or repurposing features meant for types to apply to modifiers.
163
+
3. The name `using` is general enough that it can be extended in the future if we wish.
164
+
165
+
### A general macro to specify compiler settings
166
+
167
+
There's a separate pitch on the forums that introduces a built-in macro which can enable compiler flags on a per-file basis, including enabling strict concurrency checking, strict memory safety, and warning control. This design discussion also explored using the macro for specifying actor isolation per file:
However, default actor isolation has a significant difference from the other compiler settings that the macro supported: it impacts language semantics. Default actor isolation is a language dialect, while the other compiler flags only configure diagnostics; the behavior of the code does not depend on which diagnostic control flags are set.
0 commit comments