I have discovered a situation that I am unable to find a warning-free solution for. Here's the issue:
// this is not under my control, but I *know* that this init will always be MainActor isolated.
protocol RequiresInit {
init()
}
class NonSendable {
}
@MainActor
class MyClass: RequiresInit {
let value: NonSendable
required nonisolated init() {
self.value = NonSendable() // how can I initialize this without warnings?
// this doesn't work...
MainActor.assumeIsolated {
self.value = 5 // Cannot assign to property: 'value' is a 'let' constant
}
}
}
I'm unable to come up with a combination that works here. Do I have any options?
Edit: I accidentally went too far and over-simplified my problem. The above is a more accurate version of the problem than what I originally posted.
Note that the compiler is still going to warn you that the isolated init cannot satisfy the protocol's requirements (although in Swift 5.9.2, at least, it still does - this will surely break in Swift 6… though oddly, in 5.9.2 with 'complete' concurrency checking it still does not).
I had to edit the original post because I over-simplified the problem. Sorry about that!
However, I'm not certain it's even worthwhile checking these kinds of things with anything less than a 5.10 compiler. In fact, I thought had a solution to this problem with 5.9.2, but 5.10 made it harder!
Oh, good find. I think the only way to suppress this warning in 5.10 is to initialize the isolated property through a Sendable type, e.g.
protocol RequiresInit {
init()
}
class NonSendable {
}
struct Transferred<T>: @unchecked Sendable {
let value: T
}
@MainActor
class MyClass: RequiresInit {
private let _value: Transferred<NonSendable>
var value: NonSendable {
_value.value
}
required nonisolated init() {
self._value = Transferred(value: NonSendable())
}
}
This is sort of emulating what region isolation can do, and there's no reason why region isolation wouldn't be able to determine that your original code is safe; NonSendable() is clearly in a disconnected region, so it's safe to transfer that into the @MainActor region. It still warns on main with -enable-experimental-feature RegionBasedIsolation, but as far as I can tell this is only because the conservative warning coming from the actor isolation checker isn't suppressed when region isolation is enabled.
If you find yourself doing this a lot, you can introduce a little property wrapper (or macro) to help reduce the code churn required, which you can then easily audit later on:
class NonSendable {}
// To be used carefully for transferring non-Sendable
// values to actor-isolated stored properties in nonisolated
// initializers. All uses of this may be removed once
// region isolation in SE-0414 is enabled.
@propertyWrapper
struct InitializerTransferred<T>: @unchecked Sendable {
let wrappedValue: T
}
@MainActor
class MyClass {
@InitializerTransferred var value: NonSendable
required nonisolated init() {
// Note: 'NonSendable()' is in a disconnected region -- no other value can
// reference it -- so it's safe to transfer to the main actor.
self._value = InitializerTransferred(wrappedValue: NonSendable())
}
}
Am I understanding correctly that this behaviour is a current limitation/bug and not a deliberate design choice?
Is this expected to be fixed/changed in Swift 6?