Problem about conditional conforming to a protocol

protocol A {}
struct X: A {}
struct Y: A {}

extension Array: A where Element: A {}
var array: [A] = [X(), Y()]

func bar(a: A) {}

bar(a: array)

The compiler complains: Type 'any A' cannot conform to 'A' about the bar(a: array)

Can someone explain what the problem is?

The extension defines the conformance of the Array, so I hope array is also conforming to A.

What's wrong with this assumption?

Compiler complains [some A] = [X(), Y()] with error - Type of expression is ambiguous without more context

Following code compiles:

protocol A {}
struct X: A {}
struct Y: A {}

extension Array: A where Element == any A {}

var array: [A] = [X(), Y()]

func bar(a: A) {}

bar(a: array as! A)

Is there any elegant way to do so?

Thanks. @TeamPuzel

I don't know why you're trying to do this, but you should probably use classes instead of structs here.

There is an example of chapter "Protocols" in official document "The Swift Programming Language".

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

let myDice = [d6, d12] // d6 and d12 is an instance of class Dice.

It is not technically impossible with structs; you could take inspiration from C and use pointers here, however that is not safe and just asking for undefined behavior.

I was trying to understand "protocol as types". In the example, Dice is a class, and d6/d12 are both instances of Dice. What I was trying was two structs.

It is not technically impossible with structs; you could take inspiration from C and use pointers here, however that is not safe and just asking for undefined behavior.

Your code compiles but you're likely just going to cause a memory error and crash, or worse, completely destroy the data.

You explanation is very helpful! Thanks again!

Hope you have a nice day!

It compiles for me fine without the as! cast:

....
bar(a: array)

Actually, changing the original code Element: A to Element == A make it compile.

protocol A {}
struct X: A {}
struct Y: A {}

extension Array: A where Element == A {}
var array: [A] = [X(), Y()]

func bar(a: A) {}

bar(a: array)

This is interesting. To a protocol, what does Element == A means?

Yes, and remove any is also fine.

Me too :joy:

So long as "protocol A" doesn't need to have "Self" or associated type - you are fine.

protocol A: Equatable {} // changes everything dramatically

That is because Equatable introduces ==, right?

It also confuses me.

A == B in a type constraint means that A is a type that is the same as type B, whereas A: B in a type constraint means that A is a type that conforms to protocol B.

A == B here is synonymous with A == any B because it expects B to be a type. And A: B is more or less the same as A == some B, even though the later isn't accepted as valid.

2 Likes

Nice. Your explanation makes sense.

As a summary:
A == B means A == any B, which is legitimate in Swift;
A: B means A == some B semantically , which is not allowed so far.

This perennial confusion about "protocol-as-constraint" vs. "protocol-as-type" was one of the major motivating factors for the introduction of the any A syntax. When you say Element: A you are saying that Element is some type conforming to the protocol A. When you say Element == A, you are saying that Element is the type any A.

Notably, it is (usually) not the case that any A does itself conform to the protocol A. It is difficult to see why that's the case in the most trivial examples (such as here where A is an empty protocol), but there are legitimate reasons why, in the general case, it may be impossible for any A to conform to A without a bespoke implementation for the requirements of A. IOW, it may not be possible to automatically derive a conformance any A: A for an arbitrary protocol A. For a brief example of the difficulty involved, consider if we gave A a single requirement:

protocol A {
  static func getA() -> any A
}
struct X: A {
  static func getA() -> any A { X() }
}
struct Y: A {
  static func getA() -> any A { Y() }
}
extension Array: A where Element: A {
  static func getA() -> any A { Element.getA() }
}
var array: [any A] = [X(), Y()]

func bar(a: any A) {
  print(type(of: a).getA())
}

bar(a: array) // what should this print?

(Edit: added any A in further places to help illustrate the distinction)

2 Likes

We can tweak bar slightly to present the same issue while accessing the type directly rather than through type(of:):

func bar<T: A>(a: T) {
  print(T.getA())
}

bar(a: array) // what should this print?
bar(a: array)

This is the place swift compiler complains: Type 'any A' cannot conform to 'A'

Right. What this example is attempting to illustrate is why we can't haven it be the case that any A 'automatically' conforms to A. If we said that any A did conform to A, then the example would have to compile as written (since then Array<any A> would conform to A by the conditional conformance). But it's not at all clear what we'd do then when we get to the getA() call inside of bar(a:) what does [any A].getA() return?

To perhaps make the issue even clearer, consider that we needn't initialize array with any elements at all, or indeed without even having any concrete types in our program which conform to A unconditionally, period!

protocol A {
  static func getA() -> any A
}
extension Array: A where Element: A {
  static func getA() -> any A { Element.getA() }
}
var array: [any A] = []

func bar(a: any A) {
  print(type(of: a).getA())
}

bar(a: array) // what should this print?
1 Like

Okay, I see what you meant.