So, what does Sendable mean?

Now that the first betas of Xcode 13.3 and Swift 5.6 are available to a wider audience I think it's important to circle back to the question of Sendable conformance and what makes a type Sendable. If nothing else, this thread can act as a place to centralized documentation around Sendable. What I'd really like to see, though, is a single, accurate, definition of sendability and concrete recommendations on how to ensure our manually conforming types properly meet the requirements of a protocol that has no requirements. So...

What does sendability mean?
When do type automatically conform?
What does it mean when a type doesn't automatically conform?
What do we do to manually conform a type to Sendable?

7 Likes

doesn’t it just mean thread-safe?

I believe the formal definition is something like "can be transferred across concurrency domains". The way I interpret it is sort-of like actor isolation: a Sendable type promises that any mutable program state it accesses cannot encounter a data race, in the same way an actor promises isolation of its internal state, and global actors isolate variables scattered about the program.

Generally speaking, types with value semantics are good candidates to be Sendable, as each variable is isolated from changes made to every other variable. Similarly, reference types tend not to be Sendable, as variables on different threads (running concurrently as part of different Tasks) may point to the same underlying state.

Even if a type is Sendable, that doesn't mean that its entire API surface is concurrency-safe. A value type may read or write from memory whose accesses are not always synchronised (e.g. perhaps a certain function will read data using a C API), or encounter logic errors if it expects to be running on some particular thread, suspends, and is awoken on a different thread. Reference types may also be Sendable if they only read and write from synchronised memory (e.g. if all accesses are guarded by a mutex).

When their instance state (stored properties) is known to be Sendable. Note again that this does not mean the type's entire API surface is free of bugs when executed concurrently.

That its instance state is not known to be Sendable. That doesn't mean it isn't Sendable; just that the compiler can't deduce that it is.

If a type does not automatically conform, that means it has one or more stored properties whose types are not marked as Sendable. The best approach would be to audit each of those types individually and explicitly mark them as @unchecked Sendable (meaning the compiler shouldn't check; not that you didn't check. The wording is quite unfortunate) if all mutable program state they access is properly synchronised. You may also wish to rename or deprecate any APIs which are not concurrency-safe at the same time.

3 Likes

I have bookmarked this tweet by @beccadax : https://twitter.com/beccadax/status/1471713250282672131

6 Likes

Because tweets can disappear, here's the content for other viewers (with apologies to @beccadax for speaking on her behalf):

A type can be Sendable if any of these three conditions is true:
(1) It has no mutable state.
(2) It has mutable state, but = creates a copy of that state that doesn’t share anything with the original.
(3) It has mutable state and = doesn’t prevent sharing, but you are synchronizing access somehow (e.g. @MainActor, Swift COW pattern, atomics, private queues, locks).

As a rule of thumb, most structs/enums can be Sendable as long as all of their stored properties have Sendable types; if you try to conform a struct and that isn’t the case, you’ll get an error unless you use @unchecked.
(The case where it can’t be Sendable is when you have e.g. an Int that you know is really a file descriptor—the file descriptor is actually used to access shared mutable state, but it just looks like an Int to the compiler.)
OTOH, classes can almost never be Sendable—they have to contain no mutable state or synchronize access to it, and they have to be final so that subclasses can’t break that rule. (Or they can be @MainActor, which makes them automatically Sendable.)
As far as Apple types…well, you’ll have to guess what the implementation looks like, and you could guess wrong. Generally, unless otherwise documented, most Apple APIs are “thread-safe” only when each instance is confined to one thread/queue/etc., which is non-Sendable.

12 Likes

Does anyone know if the Foundation types have been properly marked Sendable where applicable yet?

Latest Xcode 13.3 b1 has Sendable-related warnings on, and types such as URL, Data, and Date generate warnings.

Does anyone know if the Foundation types have been properly marked
Sendable where applicable yet?

They have not.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Do you know if those types in particular will be declared Sendable?

And related, until Foundation is updated, is there a way to disable Sendable checking in Xcode 13.3?

2 Likes