I was wondering why the compiler can't compile the following code:
protocol Foo { }
struct FooBar: Foo { }
struct BarFoo: Foo { }
var children = [FooBar(), BarFoo()]
Both FooBar and BarFoo inherit from Foo, so I thought this would be simple to infer. I'm using Xcode 13.2.1, and apparently this is an error without a specific type annotation like:
This is expected behavior. Protocol conformance is not like class inheritance. Protocol conformance simply asserts that objects that conform to a protocol satisfy the requirements stated in the protocol. There is no relationship between the two struct types other than they both conform to the same protocol. To make an array of heterogenous types such as these, you have to treat them as members of the Foo family, which why the existential protocol type Foo has to be used to declare the children array. This has to be done explicitly. Otherwise, the compiler thinks you are trying to create an array of heterogeneous types, and can't infer what the type of the array should be.
So the main reason this doesn't compile is because we are trying to typecast existential types? If the shared type was a class, then this would work because these are concrete types. Why is typecasting to existential types problematic though?
Now what is children ? Both [Foo] and [Baz] are valid and will work. So you need to specify which of them, or which other protocol they conform to, it is.
The compiler does not infer existential types (i.e. "protocol types") without a type annotation because it could be really unexpected. For example, if you had an array of Int values and one of them was accidentally UInt, you probably want an error telling you to construct an Int from the UInt instead of inferring the array element type as any FixedWidthInteger or some other protocol that both of these integer types conform to. It could also be very unexpected for protocols that a lot of types conform to that don't necessarily represent a sort of subtype relationship between these types, such as CustomStringConvertible. Other folks here have pointed out that there could also be type inference ambiguities if there are multiple protocols that this set of types conform to.
Finally, existential types have a lot of limitations that programmers should be aware of when using them, and an explicit type annotation is a signal that the programmer is intentionally using an existential type. This is exactly the motivation for adding an explicit keyword for these types in SE-0335.