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
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.
37
37
38
+
38
39
```
39
40
protocol P: Sendable {
40
41
init()
@@ -104,7 +105,7 @@ Use of the key path literal is currently being diagnosed because all key path li
104
105
105
106
## Proposed solution
106
107
107
-
We propose the compiler should automatically apply`Sendable` on functions and key paths that cannot capture non-Sendable values. This includes partially-applied and unapplied instance methods of `Sendable` types, as well as non-local functions. Additionally, it should be disallowed to utilize `@Sendable` on instance methods of non-`Sendable` types.
108
+
We propose the compiler should automatically employ`Sendable` on functions and key paths that cannot capture non-Sendable values. This includes partially-applied and unapplied instance methods of `Sendable` types, as well as non-local functions. Additionally, it should be disallowed to utilize `@Sendable` on instance methods of non-`Sendable` types.
let unapplied: @Sendable (User) -> ((String, String) -> Bool) = User.updatePassword // no error
153
+
let unapplied: @Sendable (User) -> ((String, String) → Bool) = User.updatePassword // no error
154
154
155
155
let partial: @Sendable (String, String) -> Bool = User().updatePassword // no error
156
156
```
157
157
158
158
**Key paths**
159
159
160
-
Key path literals are very similar to functions, their sendability could only be influenced by sendability of the values they capture in their arguments. Instead of requiring key path literals to always be sendable and warning about cases where key path literals capture non-Sendable types, let’s flip that requirement and allow the developers to explicitly state when a key path is required to be Sendable via `& Sendable` type composition and employ type inference to infer sendability in the same fashion as functions when no contextual type is specified. [The key path hierarchy of types is non-Sendable].
160
+
Key path literals are very similar to functions, their sendability could be influenced by sendability of the values they capture in their arguments and isolation of the referenced properties and subscripts. Instead of requiring key path literals to always be sendable and warning about cases where key path literals capture non-Sendable types, let’s flip that requirement and allow the developers to explicitly state when a key path is required to be Sendable via `& Sendable` type composition and employ type inference to infer sendability in the same fashion as functions when no contextual type is specified. [The key path hierarchy of types is non-Sendable].
161
161
162
162
Let’s extend our original example type `User` with a new property and a subscript to showcase the change in behavior:
163
163
164
164
```
165
165
struct User {
166
166
var name: String
167
167
168
+
@MainActor var age: Int
169
+
168
170
subscript(_ info: Info) -> Entry { ... }
169
171
}
170
172
```
@@ -214,6 +216,8 @@ Such `entry` declaration would be diagnosed by the sendability checker:
214
216
warning: cannot form key path that captures non-sendable type 'Info'
215
217
```
216
218
219
+
In the same fashion key path that references `age` (i.e. `\User.age`), which is a global actor isolated property, is non-Sendable.
220
+
217
221
## Detailed design
218
222
219
223
This proposal includes five changes to `Sendable` behavior.
These two rules include partially applied and unapplied static methods but do not include partially applied or unapplied mutable methods. Unapplied references to mutable methods are not allowed in the language because they can lead to undefined behavior. More details about this can be found in [SE-0042](https://github.com/apple/swift-evolution/blob/main/proposals/0042-flatten-method-types.md).
246
250
247
251
248
-
3. A key path literal without non-Sendable type captures is going to be inferred as key path type with a `& Sendable` requirement or a function type with `@Sendable` attribute.
252
+
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.
253
+
254
+
```
255
+
extension User {
256
+
@MainActor var age: Int { get { 0 } }
257
+
}
258
+
259
+
let ageKP = \User.age
260
+
let infoKP = \User.[Info()]
261
+
```
262
+
263
+
The type of age`KP` is `KeyPath<User, Int>` because `age` is isolated to a global actor. Similarly `infoKP` is a non-Sendable key path because `Info()` argument to a subscript reference has a non-Sendable type.
249
264
250
-
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 (same applies to keypath-to-function conversions):
265
+
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.
251
266
252
267
```
253
268
let name: KeyPath<User, String> = \.name
254
269
let otherName: KeyPath<User, String> & Sendable = \.name 🔴
255
-
let nameFn: @Sendable (User) -> String = name 🔴
256
270
```
257
271
272
+
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.
273
+
274
+
```
275
+
let name: @Sendable (User) -> String = \.name 🟢
276
+
```
277
+
278
+
The example above is accepted and is transformed by the compiler into:
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:
285
+
286
+
```
287
+
let value: NonSendable = NonSendable()
288
+
let _: @Sendable (User) -> String = \.[value] 🔴
289
+
```
290
+
291
+
This is an error because `value` has a non-Sendable type and the compiler synthesized closure that wraps the key path - `{ $0[keyPath: \.[value]] }` is going to be inferred as non-Sendable (because it captures `value`) hence non-convertible to a `@Sendable` function type.
292
+
293
+
Similarly if the conversion captures a key path that has a reference to an isolated property or subscript the implicitly generated closure is not inferred to be non-Sendable.
294
+
258
295
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:
259
296
260
297
```
@@ -273,7 +310,7 @@ Next is:
273
310
274
311
4. The inference of `@Sendable` when referencing non-local functions.
275
312
276
-
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.
313
+
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.
277
314
278
315
```
279
316
func doWork() -> Int {
@@ -291,7 +328,7 @@ Currently, trying to start a `Task` with the global function `doWork` will cause
291
328
class C {
292
329
var random: Int = 0 // random is mutable so `C` can't be checked sendable
293
330
294
-
@Sendable func generateN() async -> Int { //error: adding @Sendable to function of non-Sendable type prohibited
331
+
@Sendable func generateN() async -> Int { //error: adding @Sendable to function of non-Senable type prohibited
295
332
random = Int.random(in: 1..<100)
296
333
return random
297
334
}
@@ -311,6 +348,37 @@ If we move the previous work we wanted to do into a class that stores the random
311
348
312
349
Since `@Sendable` attribute will be automatically determined with this proposal, you will no longer have to explicitly write it on function and method declarations.
313
350
351
+
### Extending key path merging functionality to preserve sendability
352
+
353
+
Existing Key path API provides a way to join two key paths together via using instance method `appending(...)` . Overloads of this method take key path types of varying mutability as their parameters and produce a new “joined” key path of a desired mutability (read-only, writable, or reference writable).
354
+
355
+
Under the proposed semantics all overloads of this method become non-Sendable but it is possible and desirable to alleviate that and support/propagate sendability if both “base” and “appended” key paths are `Sendable`.
356
+
357
+
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:
358
+
359
+
```
360
+
extension Sendable where Self: AnyKeyPath {
361
+
@inlinable
362
+
public func appending<Root, Value, AppendedValue>(
// Both `base` and `\String.utf8.count` are Sendable key paths,
375
+
// so `appending(path:)` returns a Sendable key path too.
376
+
return base.appending(path: \.utf8.count) 🟢
377
+
}
378
+
```
379
+
380
+
Standard library would have to introduce a variety of new overloads to keep `Sendable` capable `appending(...)` on par with existing non-Sendable functionality.
381
+
314
382
## Source compatibility
315
383
316
384
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:
@@ -345,18 +413,15 @@ Accessors are not currently allowed to participate with the `@Sendable` system i
345
413
346
414
## Alternatives Considered
347
415
348
-
Swift could forbid explicitly marking non-local function declarations with the`@Sendable` attribute, since under this proposal there’s no longer any reason to do this.
416
+
Swift could forbid explicitly marking function declarations with the`@Sendable` attribute, since under this proposal there’s no longer any reason to do this.
349
417
350
418
```
351
419
/*@Sendable*/ func alwaysSendable() {}
352
420
```
421
+
353
422
However, since these attributes are allowed today, this would be a source breaking change. Swift 6 could potentially include fix-its to remove `@Sendable` attributes to ease migration, but it’d still be disruptive. The attributes are harmless under this proposal, and they’re still sometimes useful for code that needs to compile with older tools, so we have chosen not to make this change in this proposal. We can consider deprecation at a later time if we find a good reason to do so.
354
423
355
-
If we do this, nested functions would not be impacted.
0 commit comments