Hello,
Would you please help me understand how to have region-based isolation help me fixing some warnings?
My Swift 6 setup:
-
swift-6.0-DEVELOPMENT-SNAPSHOT-2024-04-22-a
-
Swift settings:
var swiftSettings: [SwiftSetting] = [ .enableUpcomingFeature("StrictConcurrency"), .enableExperimentalFeature("RegionBasedIsolation"), .enableExperimentalFeature("TransferringArgsAndResults"), ]
The warning I'd like to avoid is the following:
// Assume this function exists
func schedule(_ action: @escaping @Sendable () -> Void) { ... }
// Usage
class NonSendable { }
func run() {
let ns = NonSendable()
// ⚠️ Capture of 'ns' with non-sendable type 'NonSendable'
// in a `@Sendable` closure
schedule { _ = ns }
}
I would have expected SE-0414 Region based Isolation to detect that this code is safe. I was wrong :-)
The proposal says:
A value in a disconnected region can be transferred to another isolation domain as long as the value is used uniquely by said isolation domain and never used later outside of that isolation domain lest we introduce races.
I suppose that no transfer can happen here, because the schedule
function does not declare any isolation domain - its closure argument is nonisolated.
I tried to use SE-0430 transferring
isolation regions of parameter and result values, but it just moves the warning to another place:
// Assume this function exists
func schedule(_ action: @escaping @Sendable () -> Void) { ... }
// Transferring to the rescue?
func schedule<T>(
value: transferring T,
_ action: @escaping @Sendable (T) -> Void
) {
// ⚠️ Capture of 'value' with non-sendable type 'T'
// in a `@Sendable` closure.
schedule { action(value) }
}
class NonSendable { }
func run() {
let ns = NonSendable()
// no warning
schedule(value: ns) { _ = $0 }
}
OK, let's stop fighting the compiler. This can never work, because the schedule
method does not make enough promise, and in particular its closure argument does not define any isolation domain. End of the story.
If I change the definition of schedule
so that it defines an isolation domain, region-based isolation can work, right?
Things turn a little more complex, considering schedule
is actually defined in a protocol:
protocol Scheduler: Sendable {
func schedule(_ action: @escaping @Sendable () -> Void)
}
The protocol semantics require that actions are serialized, so I'm very close indeed from a proper isolation domain.
Is it possible to change the protocol so that each instance of a confirming type can declare its own isolation domain (and not only isolation domains of global actors?), so that the following code compiles without any warning?
// The goal
func run(scheduler: some Scheduler) {
let ns = NonSendable()
// no warning
scheduler.schedule {
// isolation specified by the scheduler instance.
_ = ns
}
}
Slight difficulty: I can't force schedulers to be actors, because I code a library that has generous minimum targets, and should run in pre-Swift Concurrency OSes.