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
Copy file name to clipboardExpand all lines: proposals/NNNN-global-actor-isolated-types-usability.md
+37-10Lines changed: 37 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,7 @@ This proposal encompasses a collection of changes to concurrency rules concernin
15
15
16
16
Currently, there exist limitations in the concurrency model around types that are isolated to global actors.
17
17
18
-
First, let's consider the stored properties of `struct`s isolated to global actors. `let` properties of such types are implicitly treated as `isolated` within the current module if they have `Sendable` type, but `var` properties are not. This poses a number of problems, such as when implementing a protocol conformance. Currently, the only solution is to declare the property `nonisolated(unsafe)`:
18
+
First, let's consider the stored properties of `struct`s isolated to global actors. `let` properties of such types are implicitly treated as `nonisolated` within the current module if they have `Sendable` type, but `var` properties are not. This poses a number of problems, such as when implementing a protocol conformance. Currently, the only solution is to declare the property `nonisolated(unsafe)`:
19
19
20
20
```swift
21
21
@MainActorstructS {
@@ -29,13 +29,13 @@ extension S: Equatable {
29
29
}
30
30
```
31
31
32
-
However, there is nothing unsafe about treating `x` as nonisolated. The general rule is that concurrency is safe as long as there aren't data races. The type of `x` conforms to `Sendable`, and using a value of `Sendable` type from multiple concurrent contexts shouldn't ever introduce a data race, so any data race involved with an access to `x` would have to be on memory in which `x` is stored. But `x` is part of a value type, which means any access to it is always also an access to the containing `S` value. As long as Swift is properly preventing data races on that larger access, it's always safe to access the `x` part of it. So, first off, there's no reason for Swift to require `(unsafe)` when marking `x``nonisolated`.
32
+
However, there is nothing unsafe about treating `x` as `nonisolated`. The general rule is that concurrency is safe as long as there aren't data races. The type of `x` conforms to `Sendable`, and using a value of `Sendable` type from multiple concurrent contexts shouldn't ever introduce a data race, so any data race involved with an access to `x` would have to be on memory in which `x` is stored. But `x` is part of a value type, which means any access to it is always also an access to the containing `S` value. As long as Swift is properly preventing data races on that larger access, it's always safe to access the `x` part of it. So, first off, there's no reason for Swift to require `(unsafe)` when marking `x``nonisolated`.
33
33
34
-
We can do better than that, though. It should be possible to treat a `var` stored property of a global-actor value type as *implicitly*`nonisolated` under the same conditions that a `let` property can be. A stored property from a different module can be changed to a computed property in the future, and those future computed accessors may need to be isolated to the global actor, so allowing access across module boundaries would not be okay for source or binary compatibility. But within the module that defines the property, we know that hasn't happened, so it's fine to use a more relaxed rule.
34
+
We can do better than that, though. It should be possible to treat a `var` stored property of a global-actor-isolated value type as *implicitly*`nonisolated` under the same conditions that a `let` property can be. A stored property from a different module can be changed to a computed property in the future, and those future computed accessors may need to be isolated to the global actor, so allowing access across module boundaries would not be okay for source or binary compatibility without an explicit `nonisolated` annotation. But within the module that defines the property, we know that hasn't happened, so it's fine to use a more relaxed rule.
35
35
36
-
Next, under the current concurrency rules, it is possible for a function type to be both isolated to a global actor and yet not required to be `Sendable`. This is not a useful combination: such a function can only be used if the current context is isolated to the global actor, and in that case the global actor annotation is unnecessary because *all* non-`Sendable` functions will run with global actor isolation.
36
+
Next, under the current concurrency rules, it is possible for a function type to be both isolated to a global actor and yet not required to be `Sendable`:
It would be better for a global actor attribute to always imply `@Sendable`.
47
+
This is not a useful combination: such a function can only be used if the current context is isolated to the global actor, and in that case the global actor annotation is unnecessary because *all* non-`Sendable` functions will run with global actor isolation. It would be better for a global actor attribute to always imply `@Sendable`.
48
48
49
49
Because a globally-isolated closure cannot be called concurrently, it's safe for it to capture non-`Sendable` values even if it's implicitly `@Sendable`. Such values just need to be transferred to the global actor's region (if they aren't there already). The same logic also applies to closures that are isolated to a specific actor reference, although it isn't currently possible to write such a closure in a context that isn't isolated to that actor.
50
50
@@ -110,7 +110,7 @@ We propose that:
110
110
Let's look at the first problem with usability of a `var` property of a main-actor-isolated struct:
111
111
112
112
```swift
113
-
@MainActor
113
+
@MainActor
114
114
structS {
115
115
var x: Int=0// okay ('nonisolated' is inferred within the module)
116
116
}
@@ -122,9 +122,25 @@ extension S: Equatable {
122
122
}
123
123
```
124
124
125
-
In the above code, `x` is implicitly `nonisolated` within the module. Under this proposal, `nonisolated` is inferred for within the module access of `Sendable` properties of a global-actor-isolated value type. This is data-race safe because the property belongs to a value type, meaning it will be copied every time it crosses an isolation boundary.
125
+
In the above code, `x` is implicitly `nonisolated` within the module. Under this proposal, `nonisolated` is inferred for in-module access to `Sendable` properties of a global-actor-isolated value type. A `var` with `Sendable` type within a value type can also have an explicit `nonisolated` modifier to allow synchronous access from outside the module. Once added, `nonisolated` cannot later be removed without potentially breaking clients. The programmer can still convert the property to a computed property, but it has to be a `nonisolated` computed property.
126
+
127
+
Because `nonisolated` access only applies to stored properties, wrapped properties and `lazy`-initialized properties with `Sendable` type still must be isolated because they are computed properties:
126
128
127
-
The programmer can still choose to explicitly mark a stored property `nonisolated` to allow synchronous access from outside the module. It is not necessary to use `nonisolated(unsafe)` if the property has `Sendable` type and the property is of a value type. Once added, `nonisolated` cannot later be removed without potentially breaking clients. The programmer can still convert the property to a computed property, but it has to be a `nonisolated` computed property.
129
+
```swift
130
+
@propertyWrapper
131
+
structMyWrapper<T> { ... }
132
+
133
+
@MainActor
134
+
structS {
135
+
@MyWrappervar x: Int=0
136
+
}
137
+
138
+
extensionS: Equatable{
139
+
staticnonisolatedfunc==(lhs: S, rhs: S) ->Bool {
140
+
return lhs.x== rhs.x// error
141
+
}
142
+
}
143
+
```
128
144
129
145
### `@Sendable` inference for global-actor-isolated functions and closures
130
146
@@ -162,8 +178,19 @@ func test() {
162
178
163
179
The above code is data-race safe, since a globally-isolated closure will never operate on the same instance of `NonSendable` concurrently.
164
180
165
-
Note that under region isolation in SE-0414, capturing a non-`Sendable` value in an actor-isolated closure will transfer the region into the actor, so it is impossible to have concurrent access on non-`Sendable` captures even if the isolated closure is formed outside the actor.
181
+
Note that under region isolation in SE-0414, capturing a non-`Sendable` value in an actor-isolated closure will transfer the region into the actor, so it is impossible to have concurrent access on non-`Sendable` captures even if the isolated closure is formed outside the actor:
182
+
183
+
```swift
184
+
classNonSendable {}
166
185
186
+
functest(ns: NonSendable) async {
187
+
letclosure { @MainActorin
188
+
print(ns) // error: task-isolated value 'ns' can't become isolated to the main actor
0 commit comments