(Yes I'm familiar with async Swift and how one uses async APIs throughout an application. In a small POC I wanted to simply things by mapping from Async to Sync. Which is not something I would do in production apps.)
Task.immediate was recently shipped and I when I read SE-0472 a couple of months ago I was under the impression that it would allow bridging async code to sync contexts. The title of SE-0472 is Starting tasks synchronously from caller context so I interpreted it as a tool for such bridging.
So I tried writing this helper function:
struct ResultIsNil: Error {}
func bridgeAsyncToSync<T>(operation: @escaping @Sendable () async throws -> T) throws -> T {
var result: Result<T, Error>?
Task.immediate {
do {
result = await .success(try operation())
} catch {
result = .failure(error)
}
}
guard let result else {
throw ResultIsNil()
}
return try result.get()
}
(In a SPM package on macOS 26, in Xcode 26, which requires platforms: [.macOS(.v26)], in Package.swift).
This code does not compile - error: Sending value of non-Sendable type '() async -> ()' risks causing data races since result is mutable.
But if we annotate @MainActor on it and use it like this, this code does compile:
import Testing
struct ResultIsNil: Error {}
@MainActor
func bridgeAsyncToSync<T>(operation: @escaping @Sendable () async throws -> T) throws -> T {
// same as above, just annotated with @MainActor
}
func get() async throws -> Int {
try await Task.sleep(for: .seconds(0.5))
return 1
}
@MainActor
func getSync() throws -> Int {
try bridgeAsyncToSync {
try await get()
}
}
@Test
func testGet() async throws {
let magic = try await get()
#expect(magic == 1)
}
@MainActor
@Test
func testGetSync() throws {
let magic = try getSync()
#expect(magic == 1)
}
The test testGet() passes, the test testGetSync() does not pass, result is always nil so ResultIsNil is thrown.
So now I'm confused.... Why does the code compile with @MainActor but act in a way such that operation inside Task.immediate never actually gets executed?
So apparently I completely misunderstood Task.immediate..?
But judging by the name and signature of the function, it really looks like it would allow what I wanted to do! And most likely I am not alone in misunderstanding it.