Skip to content

Commit 570bff1

Browse files
committed
[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.
1 parent 4d56d3a commit 570bff1

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

proposals/NNNN-tilde-sendable.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# `~Sendable` Conformance for Suppressing Sendable Inference
2+
3+
* **Proposal**: [SE-NNNN](NNNN-tilde-sendable.md)
4+
* **Authors**: [Pavel Yaskevich](https://github.com/xedin)
5+
* **Review Manager**: TBD
6+
* **Status**: **Pitch**
7+
* **Implementation**: [implementation](https://github.com/swiftlang/swift/pull/84777), [Interaction with ObjC](https://github.com/swiftlang/swift/pull/85105)
8+
* **Experimental Feature Flag**: `TildeSendable`
9+
* **Review**: [pitch](https://forums.swift.org/t/pitch-sendable-conformance-for-suppressing-sendable-inference/83288)
10+
11+
## Introduction
12+
13+
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.
14+
15+
16+
## Motivation
17+
18+
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.
19+
20+
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:
21+
22+
23+
```swift
24+
class Base {
25+
// ...
26+
}
27+
```
28+
29+
30+
31+
```swift
32+
@available(*, unavailable)
33+
extension Base: Sendable {
34+
}
35+
```
36+
37+
38+
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`:
39+
Attempting to declare a thread-safe subclass `ThreadSafe`:
40+
41+
42+
```swift
43+
final class ThreadSafe: Base, @unchecked Sendable {
44+
// ...
45+
}
46+
```
47+
48+
49+
is not possible and results in the following compiler warning:
50+
51+
52+
```
53+
warning: conformance of 'ThreadSafe' to protocol 'Sendable' is already unavailable
54+
```
55+
56+
57+
because unavailable conformance to `Sendable` is inherited by the subclasses.
58+
59+
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.
60+
61+
62+
## Proposed Solution
63+
64+
Introduce `~Sendable` conformance syntax that explicitly suppresses `Sendable`:
65+
66+
```swift
67+
// This type will never be inferred as Sendable before though it could be inferred as such.
68+
struct MyType: ~Sendable {
69+
let value: Int
70+
}
71+
```
72+
73+
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.
74+
75+
76+
## Detailed Design
77+
78+
The `~Sendable` conformance uses the tilde (`~`) prefix to indicate suppression similar to `~Copyable`, `~Escapable`, and `~BitwiseCopyable`:
79+
80+
```swift
81+
// Suppress Sendable inference
82+
struct NotSendableType: ~Sendable {
83+
let data: String
84+
}
85+
86+
// Can be combined with other conformances
87+
struct MyType: Equatable, ~Sendable {
88+
let id: UUID
89+
}
90+
91+
// Works with classes
92+
class MyClass: ~Sendable {
93+
private let data = 0
94+
}
95+
```
96+
97+
Just like with unavailable extensions, types with `~Sendable` conformances cannot satisfy `Sendable` requirements:
98+
99+
```swift
100+
func processData<T: Sendable>(_ data: T) { }
101+
102+
struct NotSendable: ~Sendable {
103+
let value: Int
104+
}
105+
106+
processData(NotSendable(value: 42)) // error: type 'NotSendable' does not conform to the 'Sendable' protocol
107+
```
108+
109+
110+
But, unlike unavailable extensions, `~Sendable` conformances do not affect subclasses:
111+
112+
```swift
113+
class A: ~Sendable {
114+
}
115+
116+
final class B: A, @unchecked Sendable {
117+
}
118+
119+
func takesSendable<T: Sendable>(_: T) {
120+
}
121+
122+
takesSendable(B()) // Ok!
123+
```
124+
125+
126+
Attempting to use `~Sendable` as a generic requirement results in a compile-time error:
127+
128+
```swift
129+
func test<T: ~Sendable>(_: T) {} // error: conformance to 'Sendable' can only be suppressed on structs, classes, and enums
130+
```
131+
132+
133+
Attempting to explicitly conform (both conditionally and unconditionally) to both `Sendable` and `~Sendable` results in a compile-time error:
134+
135+
```swift
136+
struct Container<T>: ~Sendable {
137+
let value: T
138+
}
139+
140+
extension Container: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable'
141+
extension Container: Sendable where T: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable'
142+
```
143+
144+
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`.
145+
146+
## Source Compatibility
147+
148+
This proposal is purely additive and maintains full source compatibility with existing code:
149+
150+
* Existing code continues to work unchanged
151+
* No existing `Sendable` inference behavior is modified
152+
* Only adds new opt-in functionality
153+
154+
## Effect on ABI Stability
155+
156+
`~Sendable` conformance is a compile-time feature and has no ABI impact:
157+
158+
* No runtime representation
159+
* No effect on existing compiled code
160+
161+
## Effect on API Resilience
162+
163+
The `~Sendable` annotation affects API contracts:
164+
165+
* **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.
166+
167+
## Alternatives Considered
168+
169+
### `@nonSendable` Attribute
170+
171+
```swift
172+
@nonSendable
173+
struct MyType {
174+
let value: Int
175+
}
176+
```
177+
178+
Protocol conformance is more ergonomic considering the inverse case, and it follows the existing convention of conformance suppression to other marker protocols.
179+
180+
181+
## Acknowledgements
182+
183+
Thank you to [Holly Borla](https://github.com/hborla) for the discussion and editorial help.

0 commit comments

Comments
 (0)