I just want to share a pitfall related to Sendable: currently any object can bypass Sendable checking if you want (without using any unsafe language features of course).
Normally, it is impossible to freely send non-Sendables to another isolation domain:
class A {}
func foo(_ a: A) {
Task { _ = a } // error
}
However, we can rely on the fact that Sendable is a marker protocol, and it does not have any runtime effects.
func runtimeCast<T, U>(_ t: T, _ u: U.Type) -> U? {
t as? U
}
func transformed(_ a: A) -> (any Sendable)? {
runtimeCast(a, (any Sendable).self) // always successfully
}
func bar(_ a: A) {
guard let newA = transformed(a) else { return }
Task { _ = newA } // this line is now possible, because newA is Sendable...
}
I learned this trick after reading this discussion of Sendable casting checks related to isolated conformance.
And to make things even more confusing, function types with and without @Sendable are differentiated in the runtime.
Which in turn means that value of (@MainActor () -> Void).self will differ depending on if code was compiled with SE-0434 enabled or disabled. And if you mix code compiled with different compiler settings you can get very surprising runtime failures.
why exactly does the runtime cast to Sendable succeed? do such casts work because the runtime has no knowledge of whether a type is or is not Sendable[1]?
per this comment from the referenced thread:
is this 'just' something that remains to be implemented?
and presumably the casts should either always succeed or always fail in a deterministic manner if the true 'sendability' of the type cannot be recovered âŠī¸