Swift 5.7, existentials and their usage

Hi!
I don't understand a compiler error when using existential.
I have a struct:

struct MusicClient {
    var musicTrackDidChange: () -> AnyPublisher<Result<PlayerTrack, NSError>, Never>
}

I'd like to use async sequence on it, by using .values variable defined on the Publisher extension. The variable has 2 definitions, one when Publisher's Failure is Never and one without any constraints. If I change the musicTrackDidChange declaration to use any Publisher instead of the type-erased AnyPublisher, I'd expect to still be able to use Publisher's values variable to get the async sequence. But the code doesn't compile. It fails with the message:

Member 'values' cannot be used on value of type 'any Publisher<Result<PlayerTrack, NSError>, Never>'; consider using a generic constraint instead

Why?

The values property has a parametric type AsyncPublisher<P: Publisher> where P.Failure == Never or AsyncThrowingPublisher<P: Publisher>, with Self as the parameter P.

AsyncPublisher<AnyPublisher<Output, Failure>> works out because the type parameter is a type conforming to Publisher. The existential type any Publisher<Output, Failure> however does not conform to Publisher.

This is the immediate answer to @rurza’s question, but if you’re curious as to why existentials don’t conform to their protocols, see this answer and many others that explain why the existence of Self and statics make this relationship impossible for the language to implement.

4 Likes

Oh, I did not knew that existentials do not conform to the protocol itself :flushed:
Case closed, thank you!

Hi guys!
I'm new to the swift language forum and would like to learn more about existential types.
This issue of protocol self-conformance limitation is something I had a hard time understanding at first and is something I would like to follow closely and understand more deeply. I would like to ask you for more references that explain in detail why it cannot autoconform and what solutions have been proposed for this possible limitation so far. Is there any possible solution on the horizon or is it rather something that has been discarded?

Consider the following protocol:

protocol P {
  associatedtype T
  static var secret: T { get }
}

Now consider what it would mean if (contrary to fact) the existential type any P were to conform automatically to the protocol P.

All conforming types are required to have a static property secret. Therefore, automatic conformance would mean you could use the value (any P).secret given only the code above and nothing else.

But what could that value be? What would be its type? There is no answer. The type any P does not fulfill the requirements for conforming to P.

One possible future direction is that Swift might let you manually (not automatically) conform any P to P by writing extension any P: P { … }. However, it is not theoretically possible for all existentials automatically to conform to their protocols.

5 Likes

Similarly, consider what would need to happen for any Equatable to conform to Equatable. You'd need this:

func ==(lhs: any Equatable, rhs: any Equatable) -> Bool {
  //what should this return if lhs and rhs are actually of completely unrelated types?
  //How could the compiler know what to do?
}

and if you're like "well clearly it just returns false in that situation", what about if it's any IntegerArithmetic instead, and we're implementing +?

func +(lhs: any IntegerArithmetic, rhs: any IntegerArithmetic) -> any IntegerArithmetic {
  //if lhs and rhs are completely unrelated types we should… return false? 
  //No, that doesn't make sense. What should we return? 
  //Cast them both to the largest builtin numeric type? That'll fail for BigNums. 
  //Even if you can find an answer, how could the compiler find it automatically?
}
4 Likes

Thanks to both of you, these are two very clear examples.
It is clear that not all protocols could conform to themselves automatically and the compiler could not guess it.
But you could give a custom extension to some of them in case you need it.
I really liked the example of
extension any P: P { ... }.
Is it just an idea or is it being seriously considered?