So, I was making a class, right:
final class C: Sendable {}
final class X: Sendable {
weak var c: C?
init(c: C) {
self.c = c
}
}
But the compiler goes
Stored property 'a' of 'Sendable'-conforming class 'X' is mutable
Which makes sense! It is mutable! But then I had a horrible thought:
final class C: Sendable {}
struct Weak<T: AnyObject> {
weak var object: T?
}
extension Weak: Sendable where T: Sendable {}
final class X: Sendable {
let c: Weak<C>
init(c: C) {
self.c = Weak(object: c)
}
}
And the compiler is fine with this. Which led me to question whether the weak object here can actually become nil
, and it can:
var c: C? = C()
let x = X(c: c!)
c = nil
print(x.c.object as Any)
Now, I don't think this is actually unsafe — refcounting is atomic; I don't think there's any data races here. So I think I should file a compiler bug or make an evolution proposal. But what is the right direction?
weak var
should be allowed inSendable class
, because it effectively is, anyway (unsure if this is true — is nilling when the last reference drops actually safer than reassigning in some way?)weak let
should be allowed, in the kind of vein of@_staticExclusiveOnly
(Atomic
,Mutex
) — things that can be safely modified from other tasks/threads (weak var
would still exist, the difference being whether manual reassignment is allowed)weak
should be deprecated, replaced by a stdlib@_staticExclusiveOnly Weak
struct — it's a pretty weird point in the language syntax already and there are plenty of cases where you needWeak
already (eg. collections)weak
should be disallowed inSendable struct/enum
- something else?!