Protocol negative conformance

This may not be sound, but I'm asking out of curiosity.

Has there been any discussion about a Swift feature for negative protocol conformances — the ability to explicitly declare what types cannot conform to a given protocol?

An example use case I thought of: imagine building a wrapper/container view where the content inside should not scroll. You could constrain the ⁠@ViewBuilder closure to return a type conforming to some custom protocol, but it's much more natural to say what can't conform (e.g. ⁠ScrollView) than to enumerate everything that can.

In UIKit, you can inspect a view hierarchy at runtime to catch this kind of misuse, but that's not as easy or reliable in SwiftUI's declarative model, which is what got me thinking about this at the language level.

I imagine this would be difficult to implement, especially around generics, conditional conformances, and type erasure. But I'm curious whether it's come up before and what the general sentiment has been.

Thanks!

Do you talk about the ~ operator that is used for for ~Sendable, ~Copyable and ~Escapable ? I’m not sure that this operator is usable by everyone, I think thay it might require special implementation. If not it could be the tool you are looking for.

That ~ in a protocol declaration means something different: it cancels out an implicit conformance. So ~Copyable means “not necessarily Copyable.” It’s only used with the handful of implicit protocols.

5 Likes

This, or something like this, has come up a few times before. As I recall, being able to express "types conforming to T but not U" in a generic context would make type inference undecidable (and Turing complete to boot).

Swift's type system is already Turing complete, but negation would make it Turing complete even if the existing system constraints were resolved.

2 Likes

Thanks for the clarification

Another challenge is that any library can conform any type to any protocol, so there is no way to 100% guarantee a type does not conform to a protocol at compile time. One option that can work is something like this:

protocol A {}
protocol B: A {}

func foo<T: A>(_ arg: T) { ... }

@available(*, deprecated, message: "Passing arguments that conform to 'B' is not supported")
func foo<T: A & B>(_ arg: T) { ... }

You can’t fully prevent people from using the deprecated overload (anything more serious than a depreciation seems to cause the overload resolution to fall back to the T: A overload) and my caveat above still applies, but this can be a useful option if you have an alternative API you’d like people to use instead.

1 Like

This works for Sendable

available(*, unavailable)
extension MyType: Sendable {}

If someone tries to conform MyType to Sendable they will get a compile error.

3 Likes