My take on the new proposed type `Disconnected`

(Referencing Disconnected proposal)

My TL;DR of this proposal is that it's a way for the compiler to reckon about what's truly Sendable under the hood even if the code author omitted the Sendable keyword.

I won't touch on it from a technical perspective but rather from a higher level what direction are we headed in perspective.

So, please correct if I'm wrong but I think a general motive of Swift concurrency is that it pushes more of the internal workings of concurrency to the developer so that they can have more control of the async control flow. This proposal seems to be a push in the opposite direction that no, perhaps we'll take the wheel again, maybe you don't actually know when to mark a type as Sendable. For that reason it feels contradictory to me. I think this proposal should come with a ~Disconnected so as to give control back to the developer.

This seems like something to bring up in the proposal discussion, but I don't think your understanding of the proposal is quite right (mine may not be either). From the proposal:

This proposal introduces a Disconnected type that preserves the disconnected property of a value through storage in data structures, allowing generic types to safely transfer non-Sendable values across isolation regions without requiring those types to reason about the sending effect.

In essence, it's a way to store a non-Sendable value outside its original isolation region, since sending can't be used. You can see this in the example from the proposal:

var deque = UniqueDeque<Disconnected<NonSendable>>()
deque.append(Disconnected(NonSendable()))

guard let disconnected = deque.popFirst() else { return }

Task {
    let element = disconnected.take()
    print(element)
}

I think you could also use it to go the other way: ingest a sending value, store it in a Disconnected value, then return it again later by sending back out of the storage region.

If the value was Sendable, this wouldn't be necessary, as it could be passed between regions by definition.

I overlooked this. I'll bring this up there.

Edit: Actually this may already be covered (at least implicitly) by some of the existing comments there.

While I think it would totally be fine to jump into the proposal thread, I also believe what you are talking about here is kind of nice to keep separate. And that's because this is a little different from what the proposal is about.

Sendable is a protocol, and as such it must apply to a whole type. But that's mainly useful so when the compiler encounters a particular instance of that type, it can very easily determine it is safe to cross actor boundaries. Values of type Int are always safe - easy.

However, there's a problem. There are many situations where an entire type being Sendable is too high a bar. And not just because it can be technically difficult to make some things Sendable. That's true. But also because many types neither are or should be safe to share across actors. This is actually a useful design tool. The lack of Sendable itself can be very powerful.

But there are times when the compiler is able to prove that, even without a type being Sendable, transferring a particular value is still safe. The analysis system the compiler uses to make this determination is called "region-based isolation". There's also a keyword sending that can improve the compiler's ability to do this.

However, the analysis, even when augmented with sending, still has limits. The purpose of the Disconnected type is to give the programmer yet more ways to model how a particular value is actually used, such that the compiler can both reason about and enforce the runtime safety.

How I would actually characterize this proposal is it provides a way for the programmer to model a (relatively unusual) situation that the compiler currently believes, incorrectly, is unsafe.

4 Likes

IMO Disconnected is a safe way to use nonisolated(unsafe). It’s safe because the type has a small API surface and its code is carefully reviewed.