SE-0427: Noncopyable Generics

No. It really does mean that T cannot have a Copyable requirement. In other words, it requires the absence of Copyable. There's no "maybe" about it. It's a subtle difference but an important one that I will demonstrate...

But it is an intentional and crucial part of the design I'm proposing that we make ~Copyable act as though it is a requirement in the Swift language for users. The fact that it's artificial in the current implementation does not matter.

I will not relent on it being an error without some very convincing reasoning!

The model is absolutely nonsensical if I can write T: Copyable & ~Copyable and my code still compiles with just a warning. With the ability to copy values of type T, despite having written ~Copyable, turns that syntax into just a suggestion. It should be a requirement that T isn't Copyable because that's what the user is requesting.

In fact, my goal in this work has been that people can reliably write ~Copyable on a type T anywhere, and it can always be consistently summarized as thinking "T is absent of a Copyable requirement". That aligns with the existing usage of ~Copyable as well.

Here are all of the places where ~Copyable can be written, with the currently implemented diagnostics, to demonstrate why I keep saying that it requires the absence of Copyable:


  1. Inheritance clauses for concrete types.
protocol P {}
// note: type 'S' does not conform to inherited protocol 'Copyable'

struct S: ~Copyable, P {}
// error: type 'S' does not conform to protocol 'Copyable'

Here, the protocol P implicitly inherits from Copyable because it did not opt-out of requiring it. So its conformers must also conform to Copyable. Since S opted out of conforming to Copyable via ~Copyable, S cannot conform to P.

Thus, writing ~Copyable on S most definitely made it not be required to be Copyable. In fact, if I try:

struct S: ~Copyable {}

extension S: Copyable {}
// error: struct 'S' required to be 'Copyable' but is marked with '~Copyable'

I can't add it back with this extension, since there's nothing for it to be conditional on! And an unconditional extension adding Copyable when it was required to not be Copyable is nonsense. Even if S were generic, I still can't add it back if T is already Copyable, because all conditions are then already satisfied:

struct S<T>: ~Copyable {}

extension S: Copyable {} 
// error: generic struct 'S' required to be 'Copyable' but is marked with '~Copyable'

  1. Inheritance clauses of generic nominal types
struct S<T: ~Copyable>: ~Copyable {}

Here, T does not require Copyable, and the family of types S<T> is not Copyable. Just like how the family of types S<T> is not Equatable. Now, if I also have this extension (which is required to be in the same source-file as S:

extension S: Copyable where T: Copyable {}

The family of types S, in general, is still not Copyable. There definitely exists a type you can substitute into S<T> to make it noncopyable.

This extension just adds the condition that "S is Copyable when T is Copyable". You're totally welcome to write that out, and the compiler won't complain about the redundant where clause.

Keep in mind that this is exactly like how the following extension adds the condition "S is Equatable when T is Equatable":

extension S: Equatable where T: Equatable {}

Try as I might, I still cannot make the entire family of S<T> Copyable, despite being marked ~Copyable! The compiler catches this conflicting extension too! :slight_smile:

struct S<T: ~Copyable>: ~Copyable {}

extension S: Copyable where T: ~Copyable {}
// error: generic struct 'S' required to be 'Copyable' but is marked with '~Copyable'

  1. Inheritance clauses for protocols
protocol P {}

protocol Q: ~Copyable, P {}
// error: 'Self' required to be 'Copyable' but is marked with '~Copyable'

Here, Q tries to inherit from P, which requires Copyable, but Q's Self is also required to not require Copyable, because of the ~Copyable in its inheritance clause.

That boils down to Copyable & ~Copyable: two conflicting requirements that indicates the programmer made a mistake. If the compiler were to only warn about Q actually requiring it's conformers to be Copyable, despite programmer's explicit marking, that would be terribly confusing.


  1. where clauses

This is where most of the complexity can arise, due to how Swift's generics system works. But you still can rely on ~Copyable requiring that a generic parameter does not have a Copyable requirement...

Example 1:

protocol P {}

func f<T>(_ t: T) where T: ~Copyable, T: P {}
// error: 'T' required to be 'Copyable' but is marked with '~Copyable'

Similar to the inheritance clause examples, T here was required to not require Copyable, yet in order to satisfy the requirements of P, which implicitly inherits from Copyable, T would have to be Copyable. This again boils down to Copyable & ~Copyable.

Example 2:

struct R<V> {}

func f<T: ~Copyable>(_ r: R<T>) {}
// error: 'T' required to be 'Copyable' but is marked with '~Copyable'

Here, R's generic parameter V did not opt out when it was defined, so it requires Copyable. In function f, T is required to not require Copyable. But once we attempt to substitute T in for V, we get an error, because in Swift we infer requirements for T based on its substitution here inside R.

It would be very confusing if T were still Copyable here, or only warned about. Who's requirement really wins? Does V's Copyable win or T's ~Copyable? Neither, it's an error!

Example 3:

protocol P {
  associatedtype Element
}

func f<T, U>(_ t: T, _ u: U)
  where
    U: ~Copyable, // error: 'U' required to be 'Copyable' but is marked with '~Copyable'
    T: P,
    U == T.Element
  {}

This one is more subtle, but the compiler has your back! You wanted to require U to not require Copyable, but you've used a same-type requirement that implies U must be Copyable, because the associatedtype Element has not opted-out, and thus requires Copyable.

Example 4:

class Soup {}
func f<T>(_ t: T) where T: ~Copyable, T: Soup {}
// error: 'T' required to be 'Copyable' but is marked with '~Copyable'

Here, you've required T to not require Copyable, but you've also placed a class bound on T, which means only classes that are a subtype of the class Soup can be substituted. But all classes require Copyable, thus you haven't actually made it such that a noncopyable type can be substituted for T at all! The same principle underlies the rejection of AnyObject & ~Copyable.


  1. Existentials

This is sort of a freebie, because existentials can only have a ~Copyable in it if the existential is some composition. Thus, we have the same set of rules here, as if the composition A & B were broken up into a where clause like Self where Self: A, Self: B.

protocol P {}
protocol Q: ~Copyable {}
class Soup {}

let _: any P & ~Copyable // error: composition cannot contain '~Copyable' when another member requires 'Copyable'

let _: any AnyObject & ~Copyable // error: composition involving 'AnyObject' cannot contain '~Copyable'

let _: any Soup & ~Copyable // error: composition involving class requirement 'Soup' cannot contain '~Copyable'

let _: any Q & ~Copyable // OK
9 Likes