Skip to content

Commit 1e354ba

Browse files
committed
Delete the rationale for Sendable records
... because the reality is that I just don't know how to relax this requirement. See: - https://www.youtube.com/watch?v=JmrnE7HUaDE - https://forums.swift.org/t/swiftserverconf-feedback-on-leveraging-structured-concurrency-in-your-applications/75176
1 parent 0ea5471 commit 1e354ba

File tree

1 file changed

+63
-92
lines changed

1 file changed

+63
-92
lines changed

GRDB/Documentation.docc/SwiftConcurrency.md

Lines changed: 63 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,73 @@ All safety guarantees of Swift 6 are enforced during database accesses. They are
3939

4040
The following sections describe, with more details, how GRDB interacts with Swift Concurrency.
4141

42-
- <doc:SwiftConcurrency#Non-Sendable-Record-Types>
4342
- <doc:SwiftConcurrency#Shorthand-Closure-Notation>
4443
- <doc:SwiftConcurrency#Non-Sendable-Configuration-of-Record-Types>
44+
- <doc:SwiftConcurrency#Non-Sendable-Record-Types>
4545
- <doc:SwiftConcurrency#Choosing-between-Synchronous-and-Asynchronous-Database-Accesses>
4646

47+
### Shorthand Closure Notation
48+
49+
In the Swift 5 language mode, the compiler emits a warning when a database access is written with the shorthand closure notation:
50+
51+
```swift
52+
// Standard closure:
53+
let count = try await writer.read { db in
54+
try Player.fetchCount(db)
55+
}
56+
57+
// Shorthand notation:
58+
// ⚠️ Converting non-sendable function value to '@Sendable (Database)
59+
// throws -> Int' may introduce data races.
60+
let count = try await writer.read(Player.fetchCount)
61+
```
62+
63+
**You can remove this warning** by enabling [SE-0418: Inferring `Sendable` for methods and key path literals](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md), as below:
64+
65+
- **Using Xcode**
66+
67+
Set `SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES` to `YES` in the build settings of your target.
68+
69+
- **In a SwiftPM package manifest**
70+
71+
Enable the `InferSendableFromCaptures` upcoming feature:
72+
73+
```swift
74+
.target(
75+
name: "MyTarget",
76+
swiftSettings: [
77+
.enableUpcomingFeature("InferSendableFromCaptures")
78+
]
79+
)
80+
```
81+
82+
This language feature is not enabled by default, because it can potentially [affect source compatibility](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/sourcecompatibility#Inferring-Sendable-for-methods-and-key-path-literals).
83+
84+
### Non-Sendable Configuration of Record Types
85+
86+
In the Swift 6 language mode, and in the Swift 5 language mode with strict concurrency checkings, the compiler emits an error or a warning when a record type specifies which columns it fetches from the database, with the ``TableRecord/databaseSelection-7iphs`` static property:
87+
88+
```swift
89+
extension Player: FetchableRecord, PersistableRecord {
90+
// ❌ Static property 'databaseSelection' is not concurrency-safe
91+
// because non-'Sendable' type '[any SQLSelectable]'
92+
// may have shared mutable state
93+
static let databaseSelection: [any SQLSelectable] = [
94+
Column("id"), Column("name"), Column("score")
95+
]
96+
}
97+
```
98+
99+
**To fix this error**, replace the stored property with a computed property:
100+
101+
```swift
102+
extension Player: FetchableRecord, PersistableRecord {
103+
static var databaseSelection: [any SQLSelectable] {
104+
[Column("id"), Column("name"), Column("score")]
105+
}
106+
}
107+
```
108+
47109
### Non-Sendable Record Types
48110

49111
In the Swift 6 language mode, and in the Swift 5 language mode with strict concurrency checkings, the compiler emits an error or a warning when the application reads, writes, or observes a non-[`Sendable`](https://developer.apple.com/documentation/swift/sendable) type.
@@ -128,97 +190,6 @@ You do not need to perform this refactoring right away: you can compile your app
128190

129191
Yes. Classes that can not be modified, made of constant `let` properties, are Sendable. Those immutable classes will not make it easy to modify the database, though.
130192

131-
#### FAQ: Why this Sendable requirement?
132-
133-
**GRDB needs new features in the Swift language and the SDKs in order to deal with non-Sendable types.**
134-
135-
[SE-0430: `sending` parameter and result values](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md) looks like the language feature we need, but:
136-
137-
- `DispatchQueue.async` does not accept a `sending` closure. GRDB needs this in order to accept non-Sendable records to be sent to the database, as below:
138-
139-
```swift
140-
let nonSendableRecord: Player
141-
try await writer.write { db in
142-
try nonSendableRecord.insert(db)
143-
}
144-
```
145-
146-
Please [file a feedback](http://feedbackassistant.apple.com) for requesting this DispatchQueue improvement. The more the merrier. I personally filed FB15270949.
147-
148-
- Database access methods taint the values they fetch. In the code below, the rules of [SE-0414: Region based Isolation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0414-region-based-isolation.md) have the compiler refuse that the fetched player is sent back to the caller:
149-
150-
```swift
151-
let player = try await writer.read { db in
152-
try Player.fetchOne(db, id: 42)
153-
}
154-
```
155-
156-
Strictly speaking, the compiler diagnostic is correct: one could copy the non-Sendable `db` argument into the fetched `Player` instance, making it unsuitable for later use. In practice, nobody does that. Copying `db` is a programmer error, and GRDB promptly raises a fatal error whenever a `db` copy would be improperly used. But there is no way to tell the compiler about this practice.
157-
158-
For all those reasons, GRDB has to require values that are asynchronously written and read from the database to be `Sendable`.
159-
160-
### Shorthand Closure Notation
161-
162-
In the Swift 5 language mode, the compiler emits a warning when a database access is written with the shorthand closure notation:
163-
164-
```swift
165-
// Standard closure:
166-
let count = try await writer.read { db in
167-
try Player.fetchCount(db)
168-
}
169-
170-
// Shorthand notation:
171-
// ⚠️ Converting non-sendable function value to '@Sendable (Database)
172-
// throws -> Int' may introduce data races.
173-
let count = try await writer.read(Player.fetchCount)
174-
```
175-
176-
**You can remove this warning** by enabling [SE-0418: Inferring `Sendable` for methods and key path literals](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md), as below:
177-
178-
- **Using Xcode**
179-
180-
Set `SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES` to `YES` in the build settings of your target.
181-
182-
- **In a SwiftPM package manifest**
183-
184-
Enable the `InferSendableFromCaptures` upcoming feature:
185-
186-
```swift
187-
.target(
188-
name: "MyTarget",
189-
swiftSettings: [
190-
.enableUpcomingFeature("InferSendableFromCaptures")
191-
]
192-
)
193-
```
194-
195-
This language feature is not enabled by default, because it can potentially [affect source compatibility](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/sourcecompatibility#Inferring-Sendable-for-methods-and-key-path-literals).
196-
197-
### Non-Sendable Configuration of Record Types
198-
199-
In the Swift 6 language mode, and in the Swift 5 language mode with strict concurrency checkings, the compiler emits an error or a warning when a record type specifies which columns it fetches from the database, with the ``TableRecord/databaseSelection-7iphs`` static property:
200-
201-
```swift
202-
extension Player: FetchableRecord, MutablePersistableRecord {
203-
// ❌ Static property 'databaseSelection' is not concurrency-safe
204-
// because non-'Sendable' type '[any SQLSelectable]'
205-
// may have shared mutable state
206-
static let databaseSelection: [any SQLSelectable] = [
207-
Column("id"), Column("name"), Column("score")
208-
]
209-
}
210-
```
211-
212-
**To fix this error**, replace the stored property with a computed property:
213-
214-
```swift
215-
extension Player: FetchableRecord, MutablePersistableRecord {
216-
static var databaseSelection: [any SQLSelectable] {
217-
[Column("id"), Column("name"), Column("score")]
218-
}
219-
}
220-
```
221-
222193
### Choosing between Synchronous and Asynchronous Database Accesses
223194

224195
GRDB connections provide two versions of `read` and `write`, one that is synchronous, and one that is asynchronous. It might not be clear how to choose one or the other.

0 commit comments

Comments
 (0)