|
| 1 | +# Import Objective-C completion handler parameters as `@Sendable` |
| 2 | + |
| 3 | +* Proposal: [SE-NNNN](NNNN-sendable-completion-handlers.md) |
| 4 | +* Authors: [Holly Borla](https://github.com/hborla) |
| 5 | +* Review Manager: TBD |
| 6 | +* Status: **Awaiting review** |
| 7 | +* Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183) |
| 8 | +* Implementation: On `main` behind `-enable-experimental-feature SendableCompletionHandlers` |
| 9 | +* Review: ([pitch](https://forums.swift.org/t/pitch-import-objective-c-completion-handler-parameters-as-sendable/77904)) |
| 10 | + |
| 11 | +## Introduction |
| 12 | + |
| 13 | +This proposal changes the Objective-C importing rules such that completion handler parameters are `@Sendable` by default. |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +Swift's data-race safety model requires function declarations to codify their concurrency invariants in the function signature with annotations. The `@Sendable` annotation indicates that closure parameters are passed over an isolation boundary before they're called. A missing `@Sendable` annotation in a library has negative effects on clients who call the function; the caller can unknowingly introduce data races, and [SE-0423: Dynamic actor isolation enforcement from non-strict-concurrency contexts][SE-0423] injects runtime assertions for non-`Sendable` closure parameters that are passed into libraries that don't have data-race safety checking. This means that a missing `@Sendable` annotation can lead to a runtime crash for any code that calls the API from an actor isolated context, which is extremely painful for projects that are migrating to the Swift 6 language mode. |
| 18 | + |
| 19 | +There's a large category of APIs with closure parameters that can be automatically identified as `@Sendable` functions, even if the annotation is missing: Objective-C methods with completion handler parameters. `@Sendable` is nearly always the right default for Objective-C completion handlers, and [programmers have already been searching for an automatic way for completion handlers to be `@Sendable` by default when auditing Clang headers](https://forums.swift.org/t/clang-sendability-audit-for-closures/75557). |
| 20 | + |
| 21 | +## Proposed solution |
| 22 | + |
| 23 | +I propose automatically importing completion handler parameters from Objective-C methods as `@Sendable` functions. |
| 24 | + |
| 25 | +## Detailed design |
| 26 | + |
| 27 | +If an imported method has an async variant (as described in [SE-0297: Concurrency Interoperability with Objective-C][SE-0297]) and the method is (implicitly or explicitly) `nonisolated`, the original method will be imported with a `@Sendable` annotation on its completion handler parameter. |
| 28 | + |
| 29 | +For example, given the following Objective-C method signature: |
| 30 | + |
| 31 | +```objc |
| 32 | +- (void)performOperation:(NSString * _Nonnull)operation |
| 33 | + completionHandler:(void (^ _Nullable)(NSString * _Nullable, NSError * _Nullable))completionHandler; |
| 34 | +``` |
| 35 | + |
| 36 | +Swift will import the method with `@Sendable` on the `completionHandler` parameter: |
| 37 | + |
| 38 | +```swift |
| 39 | +@preconcurrency |
| 40 | +func perform( |
| 41 | + operation: String, |
| 42 | + completionHandler: @Sendable @escaping ((String?, Error?) -> Void)? |
| 43 | +) |
| 44 | +``` |
| 45 | + |
| 46 | +When calling the `perform` method from a Swift actor, the inference rules that allow non-`Sendable` closures to be isolated to the context they're formed in will no longer apply. The closure will be inferred as `nonisolated`, and warnings will be produced if any mutable state in the actor's region is accessed from the closure. Note that all APIs imported from C/C++/Objective-C are automatically `@preconcurrency`, so data-race safety violations are only ever warnings, even in the Swift 6 language mode. |
| 47 | + |
| 48 | +### Completion handlers of global actor isolated functions |
| 49 | + |
| 50 | +Functions that are isolated to a global actor will not have completion handlers imported as `@Sendable`. Main actor isolated functions with completion handlers that are always called on the main actor is a very common Objective-C pattern, and this carve out will eliminate false positive warnings in the cases where the main actor annotation is missing on the completion handler parameter. This carve out will not add any new dynamic assertions. |
| 51 | + |
| 52 | +### Opting out of `@Sendable` completion handlers |
| 53 | + |
| 54 | +If a completion handler does not cross an isolation boundary before it's called, the parameter can be annotated in the header with the `@nonSendable` attribute using `__attribute__((swift_attr(“@nonSendable”)))`. The `@nonSendable` attribute is only for Clang header annotations; it is not meant to be used from Swift code. |
| 55 | + |
| 56 | +## Source compatibility |
| 57 | + |
| 58 | +This change has no effect in language modes prior to Swift 6 when using minimal concurrency checking, and it only introduces warnings when using complete concurrency checking, even in the Swift 6 language mode. Declarations imported from C/C++/Objective-C are implicitly `@preconcurrency`, which makes all data-race safety violations warnings. |
| 59 | + |
| 60 | +## ABI compatibility |
| 61 | + |
| 62 | +This proposal has no impact on existing ABI. |
| 63 | + |
| 64 | +## Alternatives considered |
| 65 | + |
| 66 | +### Import completion handlers as `sending` instead of `@Sendable` |
| 67 | + |
| 68 | +The choice to import completion handlers as `@Sendable` instead of `sending` is pragmatic - the experimental `SendableCompletionHandlers` implementation has existed since 2021 and has been extensively tested for source compatibility. Similarly, `@Sendable` has been explicitly adopted in Objective-C frameworks for several years, and source compatibility issues resulting from corner cases in the compiler implementation that were intolerable to `@Sendable` mismatches have shaken out over time. `sending` is still a relatively new parameter attribute, it has not been adopted as extensively as `@Sendable`, and it does not support downgrading diagnostics in the Swift 6 language mode when combined with `@preconcurrency`. The pain caused by the dynamic actor isolation runtime assertions is enough that it's worth solving this problem now conservatively using `@Sendable`. |
| 69 | + |
| 70 | +Changing this proposal later to use `sending` instead will pose source compatibility issues, because it would become invalid to have a protocol requirement that is imported with a `sending` completion handler and implement the requirement with a `@Sendable` completion handler. The same source compatibility issue exists for overridden class methods. If programmers want to take advantage of region isolation, the recommended path is to modernize the code using `async`/`await`. |
| 71 | + |
| 72 | +## Acknowledgments |
| 73 | + |
| 74 | +Thank you to Becca Royal-Gordon for implementing the `SendableCompletionHandlers` experimental feature, and thank you to Pavel Yaskevich for consistently fixing compiler bugs where the implementation was intolerant to `@Sendable` mismatches. |
| 75 | + |
| 76 | +[SE-0297]: /proposals/0297-concurrency-objc.md |
| 77 | +[SE-0423]: /proposals/0423-dynamic-actor-isolation.md |
0 commit comments