Skip to content

Commit 03ec342

Browse files
committed
[SE-0478] Revise per-file default isolation to use a new using syntax.
1 parent f014504 commit 03ec342

File tree

1 file changed

+98
-34
lines changed

1 file changed

+98
-34
lines changed
Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
1-
# Default actor isolation typealias
1+
# Default actor isolation per-file
22

33
* Proposal: [SE-0478](0478-default-isolation-typealias.md)
4-
* Authors: [Holly Borla](https://github.com/hborla)
4+
* Authors: [Holly Borla](https://github.com/hborla), [Pavel Yaskevich](https://github.com/xedin)
55
* Review Manager: [Steve Canon](https://github.com/stephentyrone)
6-
* Status: **Active Review (April 21 ... May 5, 2025)**
6+
* Status: **Returned for revision**
77
* Vision: [Improving the approachability of data-race safety](/visions/approachable-concurrency.md)
8-
* Implementation: [swiftlang/swift#80572](https://github.com/swiftlang/swift/pull/80572)
9-
* Experimental Feature Flag: `DefaultIsolationTypealias`
8+
* Implementation: [swiftlang/swift#81863](https://github.com/swiftlang/swift/pull/81863), [swiftlang/swift-syntax#3087](https://github.com/swiftlang/swift-syntax/pull/3087)
9+
* Experimental Feature Flag: `DefaultIsolationPerFile`
1010
* Previous Proposal: [SE-0466: Control default actor isolation inference][SE-0466]
1111
* Review: ([pitch](https://forums.swift.org/t/pitch-a-typealias-for-per-file-default-actor-isolation/79150))([review](https://forums.swift.org/t/se-0478-default-actor-isolation-typealias/79436))
1212

1313
## Introduction
1414

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 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.
1616

1717
## Motivation
1818

1919
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`.
2020

2121
## Proposed solution
2222

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:
2624

2725
```swift
2826
// main.swift
2927

30-
private typealias DefaultIsolation = MainActor
28+
using @MainActor
3129

3230
// Implicitly '@MainActor'
3331
var global = 0
@@ -38,12 +36,12 @@ func main() { ... }
3836
main()
3937
```
4038

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`:
4240

4341
```swift
4442
// Point.swift
4543

46-
private typealias DefaultIsolation = nonisolated
44+
using nonisolated
4745

4846
// Implicitly 'nonisolated'
4947
struct Point {
@@ -54,34 +52,71 @@ struct Point {
5452

5553
## Detailed design
5654

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:
56+
57+
> declaration -> using-declaration \
58+
> using-declaration -> `using` attribute \
59+
> using-declaration -> `using` declaration-modifier \
60+
> using-declaration -> `using` call-expression
5861
59-
* The typealias is written at the top-level.
60-
* 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.
6263

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:
6465

6566
```swift
66-
@globalActor
67-
actor CustomGlobalActor {
68-
static let shared = CustomGlobalActor()
67+
func f() {
68+
using @MainActor // error
6969
}
70+
```
71+
72+
Specifying `using @MainActor` anywhere in the file will instruct the compiler to use `@MainActor` as the default isolation for unspecified declarations:
7073

71-
private typealias DefaultIsolation = CustomGlobalActor // warning: not used for default actor isolation
74+
```swift
75+
using @MainActor
76+
77+
// Implicitly '@MainActor'
78+
struct S {}
79+
80+
// still 'nonisolated'
81+
nonisolated struct T {}
7282
```
7383

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:
7586

7687
```swift
77-
public typealias nonisolated = Never
88+
using nonisolated
89+
90+
// Implicitly 'nonisolated'
91+
struct S {}
92+
93+
// still '@MainActor'
94+
@MainActor struct T {}
7895
```
7996

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+
protocol P {}
104+
105+
// In MyClient
106+
import MyLibrary
107+
108+
using nonisolated
109+
110+
// '@MainActor' inferred from 'P'
111+
struct S: P {}
112+
113+
// Implicitly 'nonisolated'
114+
func f() {}
115+
```
81116

82117
## Source compatibility
83118

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.
85120

86121
## ABI compatibility
87122

@@ -93,23 +128,52 @@ This proposal does not change the adoption implications of adding `@MainActor` t
93128

94129
## Alternatives considered
95130

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
97132

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:
99134

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
101137

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+
private typealias DefaultIsolation = MainActor
103139

104-
```swift
105-
SwiftSetting.defaultIsolation(nonisolated.self)
140+
// Implicitly '@MainActor'
141+
var global = 0
142+
143+
// Implicitly '@MainActor'
144+
func main() { ... }
145+
146+
main()
106147
```
107148

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:
109168

110169
```swift
111-
SwiftSetting.defaultIsolation(nonisolated)
112-
SwiftSetting.defaultIsolation(MainActor)
170+
#SwiftSettings(
171+
.treatAllWarnings(as: .error),
172+
.treatWarning("DeprecatedDeclaration", as: .warning),
173+
.defaultIsolation(MainActor.self),
174+
)
113175
```
114176

177+
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.
178+
115179
[SE-0466]: /proposals/0466-control-default-actor-isolation.md

0 commit comments

Comments
 (0)