A lot of online resources say non-final classes are not Sendable by default. That means classes and their properties are not thread-safe. So why is this code not raising an error:
class Counter {
var count = 0
func increment() {
count += 1
}
}
class Tester {
var counter = Counter()
func mutate() {
Task {
counter.increment()
}
}
}
I was expecting an error to be raised at counter.increment()
since counter
isn't Sendable.
Can anyone tell why this code isn't raising an error?
You are not getting an error/warning because, as of Xcode 14.3, at least, the compiler will only perform “minimal” checks re
Sendable
. So, set the “Strict Concurrency Checking” setting to “Complete”:Next, the first problem with your code snippet at this point is really that
Tester
is notSendable
:But let’s assume that you get past that, either by making
Tester
aSendable
type or simply capture theCounter
, rather thanTester
. Then the compiler will report theSendable
problem, this time regardingCounter
:So, you asked:
Yes, classes are not
Sendable
by default.To be more precise, it just means that you have not informed the Swift concurrency system whether it is thread-safe or not. It might be. It might not be. (In this case, it is not.) You simply do not get
Sendable
inference for free, even if it was internally thread-safe, as you would with astruct
oractor
.Specifically, if a class is
final
and has no mutable properties, it is generally thread-safe, but is not automaticallySendable
. But you can just addSendable
conformance to let the compiler know that it really is. However, if the class has some mutable properties and you want to mark it asSendable
, you have to manually make it thread-safe with some manual synchronization mechanism for its mutable properties and, then, when you are done, you can mark this class as@unchecked Sendable
to let the compiler know that although the compiler cannot reasonable verify whether it is reallySendable
or not, but that you are vouching for it.But, in short, your online resources are correct and classes are not
Sendable
by default. You just need to bump up the “Strict Concurrency Checking” setting in order to see all theseSendable
-related warnings.While I suspect you know this, for the sake of other readers, we can demonstrate the lack of thread-safety with a unit test:
Or turn on TSAN, and it will produce:
But if you either add manual synchronization of the mutable variable,
count
, inCounter
, or just make it an actor, the data race is resolved and it is thread-safe. E.g.: