I think this proposal has evolved very well through several iterations. It definitely addresses what was a missing component of the overall "concurrency version 1.0" landscape and is a significant part of the puzzle. Overall, it fits well with the feel and direction fo Swift. I have put in effort to follow along through several iterations and have done a careful reading of the current version.
I would like to comment on two related issues. I hate that they are matters of bikeshedding, but on reflection, I think it is perhaps germane both to the discussion about UnsafeConcurrentValue and to the review prompt about other languages with this feature:
First, while I appreciate that the Value in ConcurrentValue is a nod to the value semantics discussion we've had before, there's something a bit incongruent about an internally synchronized reference type being referred to as a Value, even if an Unsafe one, as we've really all along been using "value" to distinguish from reference semantics.
But drop the word Value from ConcurrentValue, and you'd get a curious issue where data types are being declared Concurrent even though they don't "run." Folks have been using the term "concurrency-safe" to describe what ConcurrentValue really stands for, and if "concurrent-able" were a word or at least rolled off the tongue well enough to be a word, then our problem would be solved.
Second, I think the term "@concurrent function" is a little unfortunate: it may well be clear to users who are steeped in Swift concurrency design, but I think to other users it could be confusable.
When we speak colloquially of "concurrent functions," I think of "concurrent forEach" or "concurrent map" and not their predicates. Even as the predicates are evaluated concurrently in the context of a concurrent forEach or map, it's really the parameter in the declaration of a concurrent forEach that's "concurrent" and not the type of that parameter, which is merely "concurrent-able" (cf. our discussion about property wrappers used on parameters).
If we can help it, it would be nice to have a clearer naming to observe the distinction so that we don't have "@concurrent functions" and "concurrent functions." Sadly, again, "concurrent-able" isn't a word, and it wouldn't make for one that rolls off the tongue.
So, can there be a solution?
In the development history of this proposal, the protocol in question has taken on several names (or been considered to be related to other protocols with those names), including ActorSendable and ValueSemantics. In reading the final proposal itself, I also see that the ConcurrentValue, UnsafeConcurrentValue, and @concurrent are explained as follows:
[A] key question is: "when and how do we allow data to be transferred across concurrency domains?"
The ConcurrentValue protocol models types that are allowed to be safely passed across concurrency domains by copying the value. This includes value-semantic types, references to immutable reference types, internally synchronized reference types, @concurrent closures,...
Any class may be declared to conform to UnsafeConcurrentValue.... This is appropriate for classes that use access control and internal synchronization to provide memory safety
A @concurrent function type is safe to transfer across concurrency domains (and thus, it implicitly conforms to the ConcurrentValue protocol).
(Emphases added.)
Therefore, I would want to see if we could consider the following names:
- Values that are safe to transfer across concurrency domains should conform to
Transferrable. (I wouldn't bother with trying to incorporate "across concurrency domains" into the name, just as we have a similarly laconic Codable.)
- Classes that are safe to transfer across concurrency domains because they use access control and internal synchronization should conform to
Synchronized (or, perhaps, UncheckedSynchronization).
- Functions that are safe to transfer across concurrency domains should be
@transferrable functions.
This ends up looking remarkably like Rust's names Send and Sync, and I take that as a good sign that we're describing similar concepts with similar words.
An interesting point pops up in comparing Rust with the proposed design for Swift:
In Rust, raw pointers are neither Send nor Sync.
Rationale, as I understand it: Because they provide no safety guarantees, they cannot guarantee that they are safe to transfer across concurrency domains.
In Swift, it is proposed here that Unsafe(Mutable)(Buffer)Pointer unconditionally conform to ConcurrentValue, not merely UnsafeConcurrentValue.
Rationale: Because they are completely unsafe, it is irrelevant whether it is safe or unsafe to transfer them across concurrency domains; we do not want any limits on a user in terms of transferring a pointer across concurrency domains unsafely.
I am not sure which choice is superior, but it would be interesting to hear someone who knows Rust well as to how that community feels about the design decision in that language.