Please help me understand sendability and reference types and container objects:
final class Company {
var name: String
init(name: String) {
self.name = name
}
}
final class Employee: Sendable {
let id: String
let employer: Company // ERROR: Stored property 'employer' of 'Sendable'-conforming class 'Employee' has non-sendable type 'Company'
init(id: String, employer: Company) {
self.id = id
self.employer = employer
}
}
I feel like I should be able to pass Employee across isolation contexts as Sendable, because as a container object it is immutable and not going to change, even though it has a property Company that itself has a mutable var field. Company is a reference type and the let employer pointer address is immutable.
Mutating Company, a container of a container, is independent of mutating Employee. The top-level object container should be considered thread-safe even if the sub-container is not.
If this were allowed you could effectively 'smuggle' the non-SendableCompany across isolation domains just by wrapping it in an Employee. Consider, what's the difference between:
This isn't considered a formal 'mutation' of employer, because the notional 'value' of a class is its reference identity, not the data it contains. But regardless, we could split this out into a version which doesn't assign 'through' employer and it would still be a race:
@MainActor {
func g(_ company: Company) {
let e = Employee(id: "id", employer: company)
Task.detached {
let c = e.employer
c.name = "Data race"
}
}