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/0418-inferring-sendable-for-methods.md
+33-34Lines changed: 33 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ The partial application of methods and other first-class uses of functions have
18
18
19
19
Let’s look at partial application on its own before we combine it with concurrency. In Swift, you can create a function-value representing a method by writing an expression that only accesses (but does not call) a method using one of its instances. This access is referred to as a "partial application" of a method to one of its (curried) arguments - the object instance.
When referencing a method *without* partially applying it to the object instance, using the expression NominalType.method, we call it "unapplied."
31
31
32
32
33
-
```
33
+
```swift
34
34
let unapplied: (T) -> (() ->Void) = S.f
35
35
```
36
36
37
37
38
38
Suppose we want to create a generic method that expects an unapplied function method conforming to Sendable as a parameter. We can create a protocol ``P`` that conforms to the `Sendable` protocol and tell our generic function to expect some generic type that conforms to ``P``. We can also use the `@Sendable` attribute, introduced for closures and functions in [SE-302](https://github.com/kavon/swift-evolution/blob/sendable-functions/proposals/0302-concurrent-value-and-concurrent-closures.md), to annotate the closure parameter.
39
39
40
40
41
-
```
41
+
```swift
42
42
protocolP: Sendable {
43
43
init()
44
44
}
@@ -56,7 +56,7 @@ Now let’s call our method and pass our struct type `S` . First we should make
56
56
This should make `S` and its methods Sendable as well. However, when we pass our unapplied function `S.f` to our generic function `g`, we get a warning that `S.f` is not Sendable as `g()` is expecting.
57
57
58
58
59
-
```
59
+
```swift
60
60
structS: P {
61
61
funcf() { ... }
62
62
}
@@ -67,7 +67,7 @@ g(S.f) // Converting non-sendable function value to '@Sendable (S) -> (() -> Voi
67
67
68
68
We can work around this by wrapping our unapplied function in a Sendable closure.
69
69
70
-
```
70
+
```swift
71
71
// S.f($0) == S.f()
72
72
g({ @Sendablein S.f($0) })
73
73
```
@@ -79,7 +79,7 @@ However, this is a lot of churn to get the expected behavior. The compiler shoul
79
79
80
80
[SE-0302](https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md#key-path-literals) makes an explicit mention that all key path literals are treated as implicitly `Sendable` which means that they are not allowed to capture any non-`Sendable` values. This behavior is justified when key path values are passed across concurrency domains or otherwise involved in concurrently executed code but is too restrictive for non-concurrency related code.
81
81
82
-
```
82
+
```swift
83
83
classInfo : Hashable{
84
84
// some information about the user
85
85
}
@@ -114,7 +114,7 @@ We propose the compiler should automatically employ `Sendable` on functions and
114
114
For a function, the `@Sendable` attribute primarily influences the kinds of values that can be captured by the function. But methods of a nominal type do not capture anything but the object instance itself. Semantically, a method can be thought of as being represented by the following functions:
115
115
116
116
117
-
```
117
+
```swift
118
118
// Pseudo-code declaration of a Nominal Type:
119
119
type NominalType {
120
120
funcmethod(ArgType) -> ReturnType { /* body of method */ }
Thus, the only way a partially-applied method can be `@Sendable` is if the `inner` closure were `@Sendable`, which is true if and only if the nominal type conforms to `Sendable`.
137
137
138
138
139
-
```
139
+
```swift
140
140
type NominalType :Sendable {
141
141
funcmethod(ArgType) -> ReturnType { /* body of method */ }
142
142
}
143
143
```
144
144
145
145
For example, by declaring the following type `Sendable`, the partial and unapplied function values of the type would have implied Sendability and the following code would compile with no errors.
@@ -163,7 +163,7 @@ Key path literals are very similar to functions, their sendability could be infl
163
163
164
164
Let’s extend our original example type `User` with a new property and a subscript to showcase the change in behavior:
165
165
166
-
```
166
+
```swift
167
167
structUser {
168
168
var name: String
169
169
@@ -175,46 +175,46 @@ struct User {
175
175
176
176
A key path to reference a property `name` does not capture any non-Sendable types which means the type of such key path literal could either be inferred as `WritableKeyPath<User, String> & Sendable` or stated to have a sendable type via `& Sendable` composition:
177
177
178
-
```
178
+
```swift
179
179
let name = \User.name// WritableKeyPath<User, String> **& Sendable**
180
180
let name: KeyPath<User, String> & Sendable = \.name// 🟢
181
181
```
182
182
183
183
It is also allowed to use `@Sendable` function type and `& Sendable` key path interchangeably:
184
184
185
-
```
185
+
```swift
186
186
let name: @Sendable (User) ->String= \.name 🟢
187
187
```
188
188
189
189
It is important to note that **under the proposed rule all of the declarations that do not explicitly specify a Sendable requirement alongside key path type are treated as non-Sendable** (see Source Compatibility section for further discussion):
190
190
191
-
```
191
+
```swift
192
192
let name: KeyPath<User, String> = \.name// 🟢 but key path is **non-Sendable**
193
193
```
194
194
195
195
Since Sendable is a marker protocol is should be possible to adjust all declarations where `& Sendable` is desirable without any ABI impact.
196
196
197
197
Existing APIs that use key path in their parameter types or default values can add `Sendable` requirement in a non-ABI breaking way by marking existing declarations as @preconcurrency and adding `& Sendable` at appropriate positions:
Explicit sendability annotation does not override sendability checking and it would still be incorrect to state that the key path literal is Sendable when it captures non-Sendable values:
210
210
211
-
```
211
+
```swift
212
212
let entry: KeyPath<User, Entry> & Sendable = \.[Info()] 🔴 Info is a non-Sendable type
213
213
```
214
214
215
215
Such `entry` declaration would be diagnosed by the sendability checker:
216
216
217
-
```
217
+
```swift
218
218
warning: cannot form key path that captures non-sendable type 'Info'
219
219
```
220
220
@@ -237,14 +237,14 @@ struct User : Sendable {
237
237
238
238
1. The inference of `@Sendable` for unapplied references to methods of a Sendable type.
239
239
240
-
```
240
+
```swift
241
241
let unapplied : @Sendable (User)-> ((String, String) ->Void) = User.changeAddress// no error
242
242
```
243
243
244
244
2. The inference of `@Sendable` for partially-applied methods of a Sendable type.
let partial : @Sendable (String, String) ->Void=User().changeAddress// no error
248
248
```
249
249
250
250
@@ -253,7 +253,7 @@ These two rules include partially applied and unapplied static methods but do no
253
253
254
254
3. A key path literal without non-Sendable type captures and references to actor-isolated properties and/or subscripts is going to be inferred as key path type with a `& Sendable` requirement or a function type with `@Sendable` attribute.
255
255
256
-
```
256
+
```swift
257
257
extensionUser {
258
258
@MainActorvar age: Int { get { 0 } }
259
259
}
@@ -266,26 +266,26 @@ The type of age`KP` is `KeyPath<User, Int>` because `age` is isolated to a globa
266
266
267
267
Key path types respect all of the existing sub-typing rules related to Sendable protocol which means a key path that is not marked as Sendable cannot be assigned to a value that is Sendable.
268
268
269
-
```
269
+
```swift
270
270
let name: KeyPath<User, String> = \.name
271
271
let otherName: KeyPath<User, String> & Sendable = \.name 🔴
272
272
```
273
273
274
274
The conversion between key path and a `@Sendable` function doesn’t actually require the key path itself to be `Sendable` because the it’s not captured by the closure but wrapped by it.
275
275
276
-
```
276
+
```swift
277
277
let name: @Sendable (User) ->String= \.name 🟢
278
278
```
279
279
280
280
The example above is accepted and is transformed by the compiler into:
281
281
282
-
```
282
+
```swift
283
283
let name: @Sendable (User) ->String= { $0[keyPath: \.name] }
284
284
```
285
285
286
286
But any subscript arguments that are non-Sendable would preclude the conversion because they’d be captured by the implicitly synthesized closure which makes the closure non-Sendable:
287
287
288
-
```
288
+
```swift
289
289
let value: NonSendable =NonSendable()
290
290
let _: @Sendable (User) ->String= \.[value] 🔴
291
291
```
@@ -296,7 +296,7 @@ Similarly if the conversion captures a key path that has a reference to an isola
296
296
297
297
Key path literals are allowed to infer Sendability requirements from the context i.e. when a key path literal is passed as an argument to a parameter that requires a Sendable type:
298
298
299
-
```
299
+
```swift
300
300
funcgetValue<T: Sendable>(_: KeyPath<User, T> & Sendable) -> T {}
301
301
302
302
getValue(name) // 🟢 both parameter & argument match on sendability requirement
@@ -314,9 +314,9 @@ Next is:
314
314
315
315
Unlike closures, which retain the captured value, global functions can't capture any variables - because global variables are just referenced by the function without any ownership. With this in mind there is no reason not to make these `Sendable` by default. This change will also include static global functions.
316
316
317
-
```
317
+
```swift
318
318
funcdoWork() ->Int {
319
-
` Int.random(in: 1..<42)`
319
+
Int.random(in: 1..<42)
320
320
}
321
321
322
322
Task<Int, Never>.detached(priority: nil, operation: doWork) // Converting non-sendable function value to '@Sendable () async -> Void' may introduce data races
@@ -326,7 +326,7 @@ Currently, trying to start a `Task` with the global function `doWork` will cause
326
326
327
327
5. Prohibition of marking methods `@Sendable` when the type they belong to is not `@Sendable`.
328
328
329
-
```
329
+
```swift
330
330
classC {
331
331
var random: Int=0// random is mutable so `C` can't be checked sendable
332
332
@@ -343,7 +343,6 @@ Task.detached {
343
343
test(num)
344
344
}
345
345
test(num) // data-race
346
-
347
346
```
348
347
349
348
If we move the previous work we wanted to do into a class that stores the random number we generate as a mutable value, we could be introducing a data race by marking the function responsible for this work `@Sendable` . Doing this should be prohibited by the compiler.
@@ -358,7 +357,7 @@ Under the proposed semantics all overloads of this method become non-Sendable bu
358
357
359
358
Such could be archived by introducing new overloads to `func appending(...)` that utilize `& Sendable` for their parameter and result in an extension of `Sendable` protocol. For example:
360
359
361
-
```
360
+
```swift
362
361
extensionSendablewhereSelf:AnyKeyPath {
363
362
@inlinable
364
363
publicfuncappending<Root, Value, AppendedValue>(
@@ -371,7 +370,7 @@ extension Sendable where Self: AnyKeyPath {
371
370
372
371
This overload would be selected if both “base” key path and the argument are `Sendable` and would produce a new `Sendable` key path:
// Both `base` and `\String.utf8.count` are Sendable key paths,
377
376
// so `appending(path:)` returns a Sendable key path too.
@@ -385,7 +384,7 @@ Standard library would have to introduce a variety of new overloads to keep `Sen
385
384
386
385
As described in the Proposed Solution section, some of the existing property and variable declarations **without explicit types** could change their type but the impact of the inference change should be very limited. For example, it would only be possible to observe it when a function or key path value which is inferred as Sendable is passed to an API which is overloaded on Sendable capability:
387
386
388
-
```
387
+
```swift
389
388
funccallback(_: @Sendable () ->Void) {}
390
389
funccallback(_: () ->Void) {}
391
390
@@ -417,7 +416,7 @@ Accessors are not currently allowed to participate with the `@Sendable` system i
417
416
418
417
Swift could forbid explicitly marking function declarations with the` @Sendable` attribute, since under this proposal there’s no longer any reason to do this.
0 commit comments