Are these protocol declarations equivalent?

Are all of these declaring the same protocol refinement:

protocol Q where Self: P, Self: Equatable { … } // 1
protocol Q where Self: P & Equatable { … } // 2
protocol Q: P where Self: Equatable { … } // 3
protocol Q: P & Equatable { … } // 4
protocol Q: P, Equatable { … } // 5

?

(I guess they are, but I want to be really sure.)

I think (1==2) != 3, 3 != (4==5)

In what way are they different?

I've been thinking about this a lot over the last few days.

There's some flexible syntax here, with I think some history. The where clauses did not completely replace the colon-inheritance syntax, and they can do some things that the colon syntax can't do. And the commas are from before protocol composition with &.

These are, if I have it right:

  1. comma-separated types in a where clause
  2. explicit protocol composition with & in a where clause.
  3. protocol inheritance (maybe not the right word) like Q : P, along with a where clause
  4. protocol inheritance with a protocol composition.
  5. protocol inheritance with comma-separated protocols.

I'm using the word 'protocol inheritance' for Q : P there because the swift book uses that, but the word 'inheritance' makes me nervous.

My intuition is poorly-formed, I realize. I feel like the right way to do this would be either:

protocol Q : P & Equatable
or
protocol Q where Self: P & Equatable

in that order, preferring explicit protocol composition with & and protocol inheritance (because the syntax is simpler.)

3 and 5 are hybrids with some things in the inheritance and some in the where clause. You can express super type relationships in the inheritance position after the colon but not some other relationships like Self.Element: Hashable.

I'm not sure (and did not look up) if there's a difference between protocol composition with & and whatever happens with a comma between the protocols. Opinion: the comma should go away someday and the & should always be used.

In the first example with Self: P, Self: Equatable, the comma separates two different refinements (that are probably equivalent to the others). In some other situation where you had protocol Q where Self: P, Self.Element : Hashable the comma makes sense though. Opinion: it would be nice to find one syntax for all of this.

It is completely possible that I am missing some subtlety here. I did not put this all into a file and start running tests on it, and I did not close re-read the whole section in the Swift book or look at the compiler source today.

1 Like

Thanks for your detailed explanation!
I'm not sure whether or not you think there is some semantic difference between the (exact particular) examples I gave. If so, and since I haven't found any differences in my own (very limited) testing, I wonder if you or anyone else can provide an example program that demonstrates any difference?

Hi Jens, I don't believe there is a semantic difference based on what I understand above. But I may be ignorant or misunderstand what's going on.

What I wrote is probably more of an analysis than an explanation, if that makes sense. As I said I did not read the source code of the compiler in this area when I was writing the response above.

The source and tests to the compiler might be the best place to find a subtle difference, or prove that they are truly semantically equivalent, but obviously that's a significant research project.

Sorry if I confused the issue earlier. Like I said I have been thinking about it a lot the last few days and working through examples, but I still only have a partial understanding.

1 Like

They are logically equivalent, but I don’t know whether they all get channelled into the same implementation by the compiler. I toyed with it for a while trying to trick the compiler into revealing some sort of accidental behaviour difference, but was unable to find anything.

For example, I was pleasantly surprised that having only an explicit conformance to Q (i.e. struct Something: Q {}) still successfully produced an implicit conformance to Equatable, along with a synthesized implementation. I had figured the where clauses might not enable inferred conformances, but I underestimated the compiler.

I would consider direct inheritance to be the most idiomatic, unless the top protocol has no requirements of its own, in which case a typealias is better:

protocol Q: P, Equatable { /* ... */ }
typealias Q: P & Equatable