1
votes

Is there a way to have an array (or any generic type really) of generics with a type parameter conforming to a protocol?

protocol MyProtocol {}

struct MyStruct<T: MyProtocol> {
  let myProp: T
}

// Generic parameter 'T' could not be inferred
// Explicitly specify the generic arguments to fix this issue
let array1 = [MyStruct]()

// Value of protocol type 'MyProtocol' cannot conform to 'MyProtocol';
// only struct/enum/class types can conform to protocols
let array2 = [MyStruct<MyProtocol>]()

// Type 'Any' does not conform to protocol 'MyProtocol'
let array3 = [MyStruct<Any>]()

protocol MyProtocol2 {
  associatedtype T = MyProtocol
  var myProp: T { get }
}
extension MyStruct: MyProtocol2 {}
// Protocol 'MyProtocol2' can only be used as a generic constraint because it has Self or 
// associated type requirements
let array4 = [MyProtocol2]()

The array can have MyStructs with a different type parameter.

Ideally, this should work:

struct MyStruct2<T: MyProtocol> {
  let myProp: T
  let myFunc: (T) -> Void
}

let array = [MyStruct2</* something? */>]()
array.forEach { $0.myFunc($0.myProp) }

I have read Protocol can only be used as a generic constraint because it has Self or associatedType requirements, but that solution does not work in my situation as the items in the array are MyStruct with any type that conforms to MyProtocol.

I have also read Usage of protocols as array types and function parameters in swift and other similar questions, but the solutions also aren't appropriate.

1
I think you need an array per concrete implementation of MyStruct, array: [MyStruct<MyType>] where struct MyType: MyProtocol {}Joakim Danielson
The problem is that protocols are not types. The expression MyStruct<T: MyProtocol> implies a real type T that adopts the protocol. So under the hood you would have a type MyStruct-where-T-is-A and another type MyStruct-where-T-is-B. Those are unrelated types. You cannot make an array of these things because they have nothing in common. This may be just another variant on stackoverflow.com/questions/33112559/…matt
Maybe I misunderstood your problem, but does MyStruct really needs to be generic? If you want something like let array2 = [MyStruct<MyProtocol>](), this probably means you don't want to use a generic in MyStructrraphael

1 Answers

1
votes

To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:

protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
  let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error

The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1 and myStruct2 have nothing in common, so you cannot make an array of them.

That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1 and myStruct2, namely MyStruct<S1> and MyStruct<S2>, are unrelated.

I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1> and MyStruct<S2> are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.

Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:

class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error

We get the same compile error. Again, you might imagine that you can put cat and kitten together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat> and Animal<Kitten> are unrelated types.