Last active
January 10, 2025 02:03
-
-
Save nathanhosselton/98b7851b624eae927a82b924235f5dcd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@MainActor | |
protocol NonSendableThing: AnyObject { | |
func doSomething() async -> Int | |
} | |
@MainActor | |
final class SomeThing: NonSendableThing { | |
static let sharedExistential: any NonSendableThing = SomeThing() | |
init() {} | |
func doSomething() async -> Int { | |
return 0 | |
} | |
} | |
@MainActor | |
func someOperation() async { | |
// Why does this error with "sharedExistential cannot cross actor | |
// boundary"? Shouldn't this be thread safe via its accessor? What does | |
// it matter that the type isn't Sendable if both its access and all | |
// of its operations are actor-bound? | |
async let _ = SomeThing.sharedExistential.doSomething() | |
// This also errors but instead with "Sending `shared` risks causing | |
// data races". This is actually a less surprising diagnostic than the | |
// former, but again, shouldn't this be thread safe since `shared` is | |
// still actor-bound? | |
let shared = SomeThing.sharedExistential | |
async let _ = shared.doSomething() | |
// This does not error at all, even when using and awaiting the async | |
// variable. Why not? Isn't this still a non-sendable type crossing an | |
// actor boundary? | |
var existentialThing: any NonSendableThing = SomeThing() | |
async let _ = existentialThing.doSomething() | |
// Using `SomeThing` directly or declaring `NonSendableThing` to be | |
// Sendable works as expected since the captured objects are known to | |
// be Sendable. So I've omitted those examples. This issue isn't a | |
// blocker in my actual project, I just want to understand what the | |
// thread safety issue is here, because this seems safe to me. | |
} |
Tiny bit of coverage of that here: https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety:
More formally, a data race occurs when one thread accesses memory while the same memory is being mutated by another thread.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@jamieQ thank you for the proposal ref! from that:
so I was correct in assuming that being actor-bound implies Sendable conformance (which also answers where
SomeThing
was getting its implicit Sendable conformance), it's just the specific case of the protocol itself not implicitly getting Sendable conformance, and I guess then, by extension, existentials of that protocol.feels like this functionality could maybe maybe be added(?), but for my part I am unbothered. it's easy to mark the protocol Sendable and move on. my worry was that I was misunderstanding the implicit sendability of actor-bound types in some fundamental way. which I only arrived at because of this very specific and probably uncommon use case. so thank you so much for taking the time to clear this up given that this was basically self-inflicted, hah.
this callout is also very much appreciated. I conflated the two somewhere along the way. and while I don't understand data races quite as well, now I at least have better framing to start from for understanding Swift concurrency as a whole.