From 570bff156e8a4f94cd1a86c77e270bd4a6de7c7c Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 18 Nov 2025 15:26:23 -0800 Subject: [PATCH 1/2] [Proposal] ~Sendable Conformance for Suppressing Sendable Inference This proposal introduces `~Sendable` conformance syntax to explicitly suppress a conformance to Sendable, which would prevent automatic `Sendable` inference on types, and provide an alternative way to mark types as non-`Sendable` without inheritance impact. --- proposals/NNNN-tilde-sendable.md | 183 +++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 proposals/NNNN-tilde-sendable.md diff --git a/proposals/NNNN-tilde-sendable.md b/proposals/NNNN-tilde-sendable.md new file mode 100644 index 0000000000..b09275c94b --- /dev/null +++ b/proposals/NNNN-tilde-sendable.md @@ -0,0 +1,183 @@ +# `~Sendable` Conformance for Suppressing Sendable Inference + +* **Proposal**: [SE-NNNN](NNNN-tilde-sendable.md) +* **Authors**: [Pavel Yaskevich](https://github.com/xedin) +* **Review Manager**: TBD +* **Status**: **Pitch** +* **Implementation**: [implementation](https://github.com/swiftlang/swift/pull/84777), [Interaction with ObjC](https://github.com/swiftlang/swift/pull/85105) +* **Experimental Feature Flag**: `TildeSendable` +* **Review**: [pitch](https://forums.swift.org/t/pitch-sendable-conformance-for-suppressing-sendable-inference/83288) + +## Introduction + +This proposal introduces `~Sendable` conformance syntax to explicitly suppress a conformance to `Sendable`, which would prevent automatic `Sendable` inference on types, and provide an alternative way to mark types as non-Sendable without inheritance impact. + + +## Motivation + +When encountering a public type that doesn't explicitly conform to `Sendable`, it's difficult to determine the intent. It can be unclear whether the type should have an explicit `Sendable` conformance that hasn't been added yet, or whether it's deliberately non-`Sendable`. Making this determination requires understanding how the type's storage is structured and whether access to shared state is protected by a synchronization mechanism - implementation details which may not be accessible from outside the library. + +There are also situations when a class is not `Sendable` but some of its subclasses are. There is currently a way to expression that a type does not conform to a `Sendable` protocol: + + +```swift +class Base { + // ... +} +``` + + + +```swift +@available(*, unavailable) +extension Base: Sendable { +} +``` + + +Like all other conformances, an unavailable conformance to `Sendable` is inherited by subclasses. An unavailable conformance means that the type never conforms to `Sendable`, including all subclasses. Attempting to declare a thread-safe subclass `ThreadSafe`: +Attempting to declare a thread-safe subclass `ThreadSafe`: + + +```swift +final class ThreadSafe: Base, @unchecked Sendable { + // ... +} +``` + + +is not possible and results in the following compiler warning: + + +``` +warning: conformance of 'ThreadSafe' to protocol 'Sendable' is already unavailable +``` + + +because unavailable conformance to `Sendable` is inherited by the subclasses. + +This third state of a class not having a conformance to `Sendable` because subclasses may or may not conform to `Sendable` is not explicitly expressible in the language. Having an explicit spelling is important for library authors doing a comprehensive `Sendable` audit of their public API surface, and for communicating to clients that the lack of `Sendable` conformance is deliberate, while preserving the ability to add `@unchecked Sendable` conformances in subclasses. + + +## Proposed Solution + +Introduce `~Sendable` conformance syntax that explicitly suppresses `Sendable`: + +```swift +// This type will never be inferred as Sendable before though it could be inferred as such. +struct MyType: ~Sendable { + let value: Int +} +``` + +This syntax is only applicable to types because other declarations like generic parameters are already effectively `~Sendable` by default until they have an explicit `Sendable` requirement. + + +## Detailed Design + +The `~Sendable` conformance uses the tilde (`~`) prefix to indicate suppression similar to `~Copyable`, `~Escapable`, and `~BitwiseCopyable`: + +```swift +// Suppress Sendable inference +struct NotSendableType: ~Sendable { + let data: String +} + +// Can be combined with other conformances +struct MyType: Equatable, ~Sendable { + let id: UUID +} + +// Works with classes +class MyClass: ~Sendable { + private let data = 0 +} +``` + +Just like with unavailable extensions, types with `~Sendable` conformances cannot satisfy `Sendable` requirements: + +```swift +func processData(_ data: T) { } + +struct NotSendable: ~Sendable { + let value: Int +} + +processData(NotSendable(value: 42)) // error: type 'NotSendable' does not conform to the 'Sendable' protocol +``` + + +But, unlike unavailable extensions, `~Sendable` conformances do not affect subclasses: + +```swift +class A: ~Sendable { +} + +final class B: A, @unchecked Sendable { +} + +func takesSendable(_: T) { +} + +takesSendable(B()) // Ok! +``` + + +Attempting to use `~Sendable` as a generic requirement results in a compile-time error: + +```swift +func test(_: T) {} // error: conformance to 'Sendable' can only be suppressed on structs, classes, and enums +``` + + +Attempting to explicitly conform (both conditionally and unconditionally) to both `Sendable` and `~Sendable` results in a compile-time error: + +```swift +struct Container: ~Sendable { + let value: T +} + +extension Container: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable' +extension Container: Sendable where T: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable' +``` + +The Swift compiler provides a way to audit Sendability of public types. The current way to do this is by enabling the `-require-explicit-sendable` flag to produce a warning for every public type without explicit `Sendable` conformance (or an unavailable extension). This flag now supports `~Sendable` and has been turned into a diagnostic group that is disabled by default - `ExplicitSendable`, and can be enabled by `-Wwarning ExplicitSendable`. + +## Source Compatibility + +This proposal is purely additive and maintains full source compatibility with existing code: + +* Existing code continues to work unchanged +* No existing `Sendable` inference behavior is modified +* Only adds new opt-in functionality + +## Effect on ABI Stability + +`~Sendable` conformance is a compile-time feature and has no ABI impact: + +* No runtime representation +* No effect on existing compiled code + +## Effect on API Resilience + +The `~Sendable` annotation affects API contracts: + +* **Public API**: Adding `~Sendable` to a public type does not impact source compatibility because `Sendable` inference does not apply to public types. Changing a `Sendable` conformance to `~Sendable` is a source breaking change. + +## Alternatives Considered + +### `@nonSendable` Attribute + +```swift +@nonSendable +struct MyType { + let value: Int +} +``` + +Protocol conformance is more ergonomic considering the inverse case, and it follows the existing convention of conformance suppression to other marker protocols. + + +## Acknowledgements + +Thank you to [Holly Borla](https://github.com/hborla) for the discussion and editorial help. From 2ddfb4c0255da45dc4479f408a79de52c8dfc100 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 19 Nov 2025 17:01:27 -0800 Subject: [PATCH 2/2] ~Sendable: Update the proposal to state that conditional conformances are allowed --- proposals/NNNN-tilde-sendable.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/proposals/NNNN-tilde-sendable.md b/proposals/NNNN-tilde-sendable.md index b09275c94b..d52c8260d3 100644 --- a/proposals/NNNN-tilde-sendable.md +++ b/proposals/NNNN-tilde-sendable.md @@ -130,7 +130,7 @@ func test(_: T) {} // error: conformance to 'Sendable' can only be ``` -Attempting to explicitly conform (both conditionally and unconditionally) to both `Sendable` and `~Sendable` results in a compile-time error: +Attempting to unconditionally conform to both `Sendable` and `~Sendable` results in a compile-time error: ```swift struct Container: ~Sendable { @@ -138,7 +138,12 @@ struct Container: ~Sendable { } extension Container: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable' -extension Container: Sendable where T: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable' +``` + +But conditional conformances are allowed similarly to i.e. `Copyable`: + +```swift +extension Container: Sendable where T: Sendable {} // Ok! ``` The Swift compiler provides a way to audit Sendability of public types. The current way to do this is by enabling the `-require-explicit-sendable` flag to produce a warning for every public type without explicit `Sendable` conformance (or an unavailable extension). This flag now supports `~Sendable` and has been turned into a diagnostic group that is disabled by default - `ExplicitSendable`, and can be enabled by `-Wwarning ExplicitSendable`.