SE-0427: Noncopyable Generics

Suppose you have a type with noncopyable generic parameters. The type itself may or may not be copyable. The type might be unconditionally noncopyable, so everything is suppressed:

struct G<T: ~Copyable, U: ~Copyable>: ~Copyable {}

The type might be unconditionally copyable (an example is UnsafePointer; even if T is noncopyable, the pointer can be copied):

struct G<T: ~Copyable, U: ~Copyable> /* : Copyable */ {}

The tricky case is conditional copyability. Because of the restrictions on conditional requirements of Copyable specifically, the conditional Copyable conformance requires that some subset of generic parameters is Copyable. With G, there are three possibilities, but two are mirror images of each other:

extension G: Copyable /* where T: Copyable, U: Copyable */ {}
extension G: Copyable where T: ~Copyable /* , U: Copyable */ {}

Now, if I instantiate a conditionally or unconditionally Copyable type with generic arguments that are all Copyable, the result must be Copyable as well. This means if I start with the "universe" of copyable types, and apply a bunch of type constructors that might be conditionally copyable, the result is always copyable; I cannot escape my "universe".

On the other hand, the first case of an unconditionally noncopyable type is different, because it allows you to construct a noncopyable type even if you didn't utter ~Copyable in your own source code.

So another way to change the extension rule is to say that there are no default conformance requirements in an extension of an unconditionally noncopyable type.

In an extension of a concrete type, Self is the generic type with the identity substitutions applied, so like G<T, U> here. Then, the requirement G<T, U>: Copyable simplifies into exactly the conditional requirements of Copyable.

I think this almost gets you all of the way, however the case of an unconditional Copyable conformance might not give you the right behavior. For example, given struct UnsafePointer<T: ~Copyable> /* : Copyable */ {}, an existing user who writes extension UnsafePointer would get extension UnsafePointer where Self: Copyable, which is UnsafePointer<T>: Copyable which simplifies away entirely (there's no implied requirement that T: Copyable because it doesn't need to be for UnsafePointer<T> to be copyable). So now your extension gives you a noncopyable T, which might not be what you wanted.

On the subject of redundant requirements in general:

So back in the day (Swift 5.7 or 5.8, I forget now) we used to diagnose redundant generic requirements. This was disabled by default at some point and then I removed the code recently. So writing T: Copyable anywhere is just silently ignored. The compiler happily accepts this for example even though the entire where clause is pointless:

func f<T: Sequence>(_: T) where T: Sequence, T.Iterator: IteratorProtocol {}
6 Likes