Hello,
SE-0296 Async/await states:
Note that we follow the design of throws in disallowing overloads that differ only in async:
func doSomething() -> String { /* ... */ } // synchronous, blocking func doSomething() async -> String { /* ... */ } // asynchronous // error: redeclaration of function `doSomething()`.
This creates an issue for evolving the GRDB SQLite toolkit, and I look for advice.
I wish users could use the same method names in both synchronous and asynchronous contexts. For example: read
:
let connection = /* some database connection */
// Synchronous context
func syncFunction() throws {
let value = try connection.read { ... }
}
// Asynchronous context
func asyncFunction() asyncthrows {
let value = try await connection.read { ... }
// ~~~~~
}
But I face "Invalid redeclaration" compiler errors, as expected according to the proposal.
Context: why my request is meaningful
The desire for both facets comes from as a trade-off:
SQLite is a synchronous C API. Some users rely on synchronous database accesses. For example, users who write scripts. Generally speaking, SQLite skills are rewarded in GRDB, and some users just expect synchronous APIs to exist. On top of that, SQLite is fast, so asynchronous accesses are not always needed.
For example:
// Certainly not worse than calling Data(contentsOf: URL)
func fetchPlayerCount() throws -> Int {
try connection.read(Player.fetchCount)
}
GRDB also exposes asynchronous APIs, for two reasons. Some database accesses are slow, and off-loading database jobs off the main queue would require too much ceremony if asynchronous APIs were not readily available. The second reason is that the scheduling of some database accesses are not important. For example, when you access the database after a network access, one mainly cares about not blocking the main thread.
Asynchronous accesses are typically found in the Combine GRDB publishers:
func downloadAndSavePublisher() -> AnyPublisher<Void, Error> {
downloadPublisher()
.flatMap { value in
connection.writePublisher { try save($0, value) }
}
.eraseToAnyPublisher()
}
The above function would really be enhanced with async/await:
// Much Better
func downloadAndSave() async throws {
let value = try await download()
try await connection.write { try save($0, value) }
}
But I need to be able to define overloads for read
and write
What are my options?
-
Should I rename my async variants with some funny name?
await asyncRead()
? But the proposal itself wants to avoid C#'s pervasive Async suffix.. -
Should I wait until overloads become allowed?
For example, the new Core Data apis described in the WWDC21 conference Bring Core Data concurrency to Swift and SwiftUI face the same problem. They worked around the overload error by defining async methods with a different signature, but they still have a problem with default values.
await moc.perform { ... }
does not pick the expected async method, due to the existence of the non-asyncmoc.perform { ... }
method:func myFunction(moc: NSManagedObjectContext) async { // Compiler warning: No 'async' operations occur within 'await' expression await moc.perform { ... } // No warning, but it's not possible to use the default // value of the `schedule` argument. await moc.perform(schedule: .immediate) { ... } await moc.perform(schedule: .enqueued) { ... } }
Will the language evolve and allow overloads that differ only in async, in order to accomodate for Core Data apis?
Or will Core Data "fix" its API with some "funny names"?
I do not expect hints and clues about the future of Core Data apis, of course. But I would appreciate hints and guidance from the authors of async/await!