SE-0302 (second review): Sendable and sendable closures

  • What is your evaluation of the proposal?
    +1
  • Is the problem being addressed significant enough to warrant a change to Swift?
    yes
  • Does this proposal fit well with the feel and direction of Swift?
    yes
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    I think the simplicity here is fantastic.
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    Unfortunately catching up late due to busyness. Though read this current proposal thread and the proposal in full.

My 1 and only question mark revolves around keypath literals.

However, to ensure that it is safe to share key paths, key path literals can only capture values of types that conform to the Sendable protocol. This affects uses of subscripts in key paths

I'm wondering why this kind of breaking change could not be avoided? I'd assume that only where keypaths are used within some concurrent context that captured variables would require Sendable. Possibly this was covered in a previous review, and if so, my apologies.

Thanks, and truly awesome proposal overall.

Or perhaps:

  • @sendable -> @concurrentSafe
  • Sendable -> ConcurrentSafe
  • @unchecked Sendable -> @unchecked ConcurrentSafe

+1, I'm very happy with how this proposal is coming together overall :slight_smile:

The name

It's fine :wink: :+1:

We have had numerous names in something I worked for this concept. Including the quite "pure" from a model perspective ActorMessage but here we're talking about not only specifically actors, so something around "sending message" sounds fine, and as such Sendable sounds good to me :+1: It definitely isn't "pure" or value type as was discussed a few times already, there are various ways to ensure a value is safe to be sent around between actors, so no name that describes the specific implementation technique will be a good fit here.

typealias DistributedSendable would be for example Codable & Sendable which I also quite like.

Future: How do we express "not-Sendable"

In presence of automatic Sendable derivation (which I think is the right thing to do, thanks so much!), there may be very rare occasions where one would want to prevent a type getting automatically Sendable conformed.

In rust this is done by

#![feature(negative_impls)]

struct Thingy(...); // has some magic meaning and should not be shared/sent

impl !Send for Thingy {} // Thingy is NOT Send

I guess the existence of "modifiers for conformances" opens up the door for this... The same way we can conform to actor X: isolated Protocol we could invent some spelling for this "opt out" conformance; Spitballing here... struct DontSendMe: not Sendable :wink:

Are we considering such thing as well, or rather leaving it out until some later point? I guess it's a rare situation, but it can happen so some way to spell it would be nice to have the model be complete.

1 Like

How about struct DontSendMe: !Sendable
is it possible?

bang in Swift while means negation in some places — I’d argue that most of the time when I see bang in Swift it is about forcing something like try! Or force unwrapping.

class ImAmSafe: Sendable! {...} // instead of unchecked Sendable.

The Core Team talked about this today, and we had an idea that we'd like one last bit of feedback on. We have decided to accept Sendable as the protocol name, borrowing the term of art sending from Rust for the act of sharing/moving values between different concurrency domains, which otherwise doesn't have a shortening that we believe to be acceptable. We were talking about what to call the function attribute — whether it should be something more "on the nose" for concurrency, like @concurrent, or whether it should align with the protocol name, like @sendable — when we realized that it was actually interesting to consider just using the protocol name, like @Sendable. This very neatly expresses the new capability introduced by the constraint, which is that the function value can be safely sent between concurrency domains, and thus that the function type conforms to Sendable. It also expresses the main restriction imposed by the constraint, which is that the captured values must conform to Sendable.

Grammatically, this would be a new use of user-defined attributes. For now, it would be limited to just the Sendable protocol, but in the future, we could allow this to be extended to apply to other protocols whose conformances can be autogenerated, such as Hashable or Codable. If Swift ever gains the ability to metaprogram a conformance to an arbitrary protocol, this could take advantage of that. We needn't design this feature right now; it's sufficient to see that this is a compelling future direction to take the language.

For these reasons, the Core Team is tentatively in favor of adopting the spelling @Sendable for the function type constraint in SE-0302. However, this is a novel idea which we haven't had much time to think about; more importantly, it's an idea that we haven't given the community an opportunity to think about. It would be inappropriate for us to immediately settle on it without discussion. Therefore, we are extending the period of this review until this Friday, March 12th, to allow the community to provide feedback on this new spelling.

All other aspects of SE-0302 will be accepted as proposed.

17 Likes

+1, this is what I suggested when the name was ConcurrentValue:

The purpose of @concurrent is to support function values that conform to ConcurrentValue and can therefore be used in contexts that require ConcurrentValue . The attribute-based design feels somewhat ad-hoc and not scalable. For example, we have discussed function values that could conform to Equatable (using equatable captures combined with source identity).

How would additional protocols fit into the current design? Would we introduce new attributes every time we want function values that have a new conformance (for example, an @equatable )?

I'm wondering if you have considered a more general approach. The immediate option that comes to mind is to use an @ConcurrentValue attribute on the function. This would allow @Equatable , @Hashable , and any other conformances that could make sense in the future.

7 Likes

Ah, sorry, I missed that in my write-up to the Core Team. You absolutely deserve credit there.

4 Likes

Could I ask for a minor clarification though: is such spelling intended to be applied exclusively to function types or (potentially, in the future) all types in general? Because in the first case, since we lack any ability to manually make functions conform to protocols, this could be a bit misleading.

This is intriguing. As long as there isn't going to be ambiguity among the various kinds of user-defined attributes (which thus far include result builders, property wrappers, and now auto-generated conformances), I think this is very reasonable. It does very nicely settle any question that we want the function attribute to mean nothing more or less than conformance to the protocol.

Just to function types. @A @B (Int) -> Float means "the type of functions from Int to Float that conform to the protocols A and B". It is a different type from (Int) -> Float, although it is a natural subtype. This is a way of creating restricted function types, which is different from the potential ability to make unrestricted function types conform to protocols.

In general, supporting this for arbitrary protocols would require representation changes to function types; it does not for Sendable because it's just a marker protocol and can be fully enforced at compile time.

1 Like

Thanks!

Right, so this is what makes me believe such feature can become misleading: seeing @Sendable fully replicate the protocol name would make me try applying such an attribute with other — even if only marker — protocols, but also here you mention that something like @Equatable could be possible.

Overall, I think it would be easier for users to learn that @sendable makes functions behave like Sendable than to learn the list of protocols that can be applied to functions. This is a very minor nit-pick, sure, but the difference in spelling does somewhat suggest that it's a compiler-defined feature, not what users are allowed to do with arbitrary protocols.


I think such spelling (i.e., with capitalized protocol name) makes sense if users will be allowed to apply such attribute with arbitrary user-defined marker protocols in the future, and they only would need to learn the exceptions of @Equatable or @Hashable or whichever non-marker protocols could be adopted by function types. Otherwise, it's a bit magical and doesn't really align with users' expectations that a protocol can be adopted manually if one tries hard enough.

+1
Excellent refinement.

It seems we just dodge the ambiguity. The result builders, the property wrappers, and the "autogenerated conformance" would be using either Type name or Protocol name, both of which share the namespace :thinking:. Except when someone has X that is both result builder and property wrapper ofc.

Currently all of the custom-attribute types have to opt in to being an attribute in some specific way (e.g. the @propertyWrapper attribute), and that determines how they're interpreted. A single type can't opt in to being an attribute in multiple ways, so there's no technical ambiguity. Maybe we'll have to do that here when we generalize it beyond Sendable, so that protocols used this way can't be used directly as e.g. result builders or protocol wrappers. Although I really don't know why you would use a protocol for either of those in the first place, and indeed you would need some significant enhancements to protocols to even try.

I don't think it's a real problem that nothing about what interpretation a specific custom attribute has is obvious from the syntax alone — or at least, to the extent it's a real problem, it applies to attributes/modifiers/keywords in general, not just to custom attributes. Attribute names should be well-chosen so that a reader's first guess is close enough to not be wildly misleading.

4 Likes

This is what we're saying, that there's a plausible path of future generalization that would let you do this to arbitrary protocols (or at least a growing set of them), and so we should use a spelling that makes this fit with that rather than giving it a spelling that will stand out as a special case.

3 Likes

Oh, sorry, I assumed that this comment

meant that such path could lead to undesirable complexity and should be avoided, except for marker protocols. No further questions then!

+1 I like it.

I am assuming @autoclosure @Sendable is supported. I don’t think I saw it mentioned.

@unchecked is not sitting right with me. It’s a brand new feature in the language.

Can it be underscored until it has a proper review? @_unchecked will allow folks to know this in an internal feature.

@unchecked in being added unchecked into the language. (Sorry)

1 Like

Sorry, but could you clarify what you feel was improper about the current review? Because this use of @unchecked was explicitly part of this review.

If you have more feedback about it, well, we’re not sticklers about review periods, and nothing’s written in stone; you can just say what you mean to say, and we’ll take it into consideration.

Terms of Service

Privacy Policy

Cookie Policy