SE-0427: Noncopyable Generics

Silly me. I thought “noncopyable” refers to our design. It’s just so fresh, and original, you can’t imitate it.

21 Likes

We're working to ensure that it is possible to retrofit existing types. You can see what the Swift standard library is doing in these two PRs. Once we understand how that's worked out, I expect we'll write up some guides for other people who want to do this.

As usual, the biggest problem will be for libraries that want to support back-deployment. Allowing clients to compile with a newer version of the library and then use an older version requires that the older version behaves correctly in the new scenarios.

2 Likes

It makes sense but i still think it requires too much theory to come to an understanding that struct Pair<Copyable>: ~Copyable is move only and struct Pair<Copyable: ~Copyable>: ~Copyable is only conditionally move only; and that this distinction rears its head in extensions is doubly confusing. I think there will always be some pain-points since the concepts are based in negation, but this aspect seems more confusing than the other parts of the proposal.

It seems to me declaring a type a unconditionally copyable is different from conditionally copyable and that they use the same syntax will always be confusing.

3 Likes

Also I think he proved the point with

  • Say I create a protocol Parseable, which means that the type provides an init?(_ from: String). Types in Swift are, by default, non-Parseable (unsurprisingly, since I can't go add it to every type out there).

vs

  • There's a protocol Copyable, which means you can copy a value. Types in Swift are, by default, Copyable. The compiler adds this conformance automatically.

In that additive processes generally require different techniques than subtractive processes, and to work around that Copyable, previously a fundamental nature of the material, could get changed to a conformance.

No other protocol works this way (yet ?) Every type must acknowledge its existence and EVERY type must have an opinion about it? What does it even mean for a type to be neither Copyable nor ~Copyable? I understand the compiler chooses Copyable for the developer but, if this is implemented, fundamentally now Swift types are no longer at their core Copyable, they are Copyable = Undefined.

Does that mean if ~ becomes a general thing as soon as a person writes a protocol the compiler will have to add ~DevProtocol to everything in that codebase behind the scenes? Because we all know if ~ is only allowed for Swift and Apple protocols, people will complain.

Actors can be ignored, you can just use a Class. Honestly, you can still ignore async if you want to. This changes the nature of variables from "I AM a Copyable thing! (But you can not if you jump through a hoop)" to "Copying? Whatever. The Compiler tells me I should try to make that easy for you" Its not a "feature add", it's a philosophy change.

I think this will all be figured out, and I get the value of having the compiler be able to check your work if its important copies of type not be made, but my fundamental point is that isn't surprising people are struggling with it, and "Its fine" is maybe not the best messaging.

1 Like

This is not correct. Both are non-copyable, and can be made conditionally copyable if the type provides a conditional conformance to : Copyable driven by the type of the generic placeholder (which I'd suggest not calling Copyable, the name of a protocol, even though that will work, as it may lead to trauma :)

The only thing you can't make conditionally copyable is a struct with no generic placeholder i.e. struct Concrete: ~Copyable. Same as you can't make struct Concrete conditionally printable or squishable. The reason being, there's nothing to hang the conditional conformance on. extension Concrete: SomeProtocol where <what goes here?>

Now it so happens that conditionalizing copyability on anything other than the copyability of a generic placeholder type is going to be extremely rare. But that's not fundamental to the model, that's just how things work out. But there are other options e.g. extension SomeContainer: Copyable where Element: Trivial (i.e. you can copy this type only if it contains trivial elements).

2 Likes

That is a misunderstanding of ~Copyable: every type that is Copyable is also ~Copyable, in the same way that every type that is Hashable is Equatable, etc. This makes sense because any value of a copyable type can obviously just be not copied. This proposal merely makes it possible to express types that are ~Copyable but not Copyable.

4 Likes

to be honest, this isn’t making things easier to understand for me

2 Likes

Some things are Printable, some things aren't. If you're given a thing, and not told it's Printable, that doesn't mean it isn't, it just means you don't know if it is. So if you have a T, it may or not be printable.

The same is true for Copyable. Except that by default, code in Swift assumes all generic placeholders are copyable unless you tell it otherwise. ~Copyable is how you do that.

2 Likes

I was imprecise: "What does it even mean for a type to conform to neither Copyable nor ~Copyable?"

I believe the counter argument is "But The Compiler will make sure anything unspecified is assigned a :Copyable so that won't happen."

But I really could be misunderstanding - so this is what I'm taking away (I promise I did read the pitch), thanks in advance for clearing anything up:

The grammar around copying is being presented as 2 protocols. It may be different under the hood, but that's the grammar. Protocols are independent currently as there is no formal way to ~ a protocol. They can have conflicting definitions but at the moment there is no way to have a protocol exclude/remove the presence of a variable or function. Hence the need to refactor everything that made things fundamentally copyable out into a Protocol (as these types don't require the developer to implement anything, that's some nuance). We've all probably done that flavor of refactoring. And now for the code to keep working everything has to get assigned that new protocol, Copyable. That makes room for the new conformance requirements of the independent protocol ~Copyable.

The underlying nature of that original type (which is everything) has changed.

Types generally don't HAVE to have an opinions about any protocols. Now failure to conform to one or the other of these protocols would be an error.

Two things would be different now:

  • Swift no longer bakes in the ability to copy, its Copyable by Compiler fiat.
  • Everything must say whether it is Copyable or ~Copyable. Nothing else is like that (?)
    • A choice has been intro the universe where none was before.
      • And choices are forks.
        • People will have to decide or be decided for. Another choice, another fork.
          • No matter which state configuration gets selected, someone will have questions, and perhaps be angry. (Why do i have to think about this? / Why did you make this choice? / This is controllable, why is this out of my control? )

ETA: My impression was that conforming something to ~Copyable was under the hood removing "weak" compiler conformances of Copyable but would not do so for developer provided ones?

And that would be NEW.

Copy-ability used to be solid ground. Now it isn't. And that may be a useful and valuable change, but it is radical.

1 Like

Could we do something like for the concurrency model where if one doesn’t use the feature in the module the compiler treat it as it was before i.e. everything is copyable but as soon as one use the feature in the module the compile switch to everything is not copyable and one need to be explicit everywhere about copyability ?

That is, essentially, what is being proposed.

Types like Optional will be able to hold non-copyable types, but people who don't know about that feature will be able to keep using them like they always have.

People who opt in to wanting to write code that manipulates non-copyable types will be able to do so. The trade-off being, these people will need to apply more syntax to do this.

4 Likes

Indeed—and hence, this proposal.

4 Likes

That is definitely true. Supressing copyability is a big new feature for Swift. It unlocks some major new capabilities. But when you unlock those capabilities, you enter a new world where many things that were previously effortless are now fiddly and annoying, and sometimes not even possible. This is a feeling familiar to people used to writing Rust.

The goal is to keep this fiddlyness further up the learning curve. If done right, people new to Swift (including experienced programmers coming from other languages) will not bang their head against this feature early on in their journey.

This comes as a trade-off. When writing non-copyable-capable code in Swift, you will have to be more verbose. There will even be some foot water pistols (oh I forgot to make this extension ~Copyable, I gotta go do that/file an issue asking for that, so I can use it on non-copyable types).

Time will tell if these are the right trade-offs. I suspect they are. If they aren't, we can tweak them, perhaps under a future language variant.

13 Likes

Indeed! :grinning:

It's the "relax, its no big deal, you won't even notice" posture that I think people mean to be reassuring that I'm taking issue with. This is a pretty big deal. "We're safe because we copy" (simplification) to "We can be safe and not copy" is kinda big, will be impressive to have pulled off, and a fundamental change to how the language interacts with information.

1 Like

Sorry, I misspoke here.

I was thinking of / my impression of a new verbosity around Copyable types in mixed ~Copyable/Copyable codebases.

Yes, please! I've been waiting for this since inception of non-copyable types. It's going to unblock one very important piece of work for me

I think a better analogy is Int and Int?. The latter might be an Int, or it might not. That's usually all ~ means in protocol context.

I've said before and I won't rehash the details, but I still feel that the chosen syntax is the biggest problem. Simply using Copyable? for constraints and !Copyable for conformances would have saved a lot of confusion.

This relates closely to the key hump I had to surmount to grok this, which maybe others are facing as well: the distinction of "what it is" vs "what I need from it". Most of the time when you write a protocol clause - i.e. when you're defining generic functions and properties, conditional conformance requirements, etc - you're not saying it is or isn't copyable, you're saying what you need from it.

…where T: Copyable is saying it must be copyable; that you need that functionality from it.

…where T: ~Copyable is the [awkward] syntax for saying it does not have to be copyable. You're not saying it's not copyable, just that it doesn't have to be copyable for your purposes. It could be copyable; you don't care.

It feels weird at first because you never had to express these notions before, because there weren't any implicit protocol conformances & requirements that you could opt out of. But it makes sense functionally.

The exception of course is when you're defining the type itself; that's when you're actually saying what it is. Which is the small but significant crack in this mental model, because saying struct Foo: ~Copyable is the one time where you're saying it is not copyable (you cannot conform it later; it is the final word on the matter for Foo). It'd be more intuitive if you phrased it struct Foo: !Copyable. If you can substitute that in your mind as you're working, I find it much easier.

5 Likes

Makes me wonder if maybe we missed the boat and should have spelled it ~NonCopyable with the tilde meaning “accepts” or “allows”. Then it would read T accepts non-copyable types which I think is a bit easier to explain.

Maybe what I’m about to say is abstract nonsense, but here’s the deal: “what I need from it” and “what it is” are the same thing for a generic parameter. It helps to think that you’re declaring a new type named T, because that’s what you’re doing, and in Swift we describe “what I need” by writing down these requirements. I get a new type named T, and I can work with values of this type, and “what it is” is completely described by requirements (Compare to C++ where “what I need from it” is implicit in the template body and “what it is” is just whatever the hell I substitute in)

5 Likes