protocol Animal {
func makeNoise()
static var species: String { get }
}
struct Dog: Animal {
func makeNoise() { print("Woof") }
static var species: String = "Canus familiaris"
}
struct Cat: Animal {
func makeNoise() { print("Meow") }
static var species: String = "Felis catus"
}
var animal: Animal // `Animal` is used here as a type.
animal = Dog()
animal.makeNoise() // Prints "Woof".
animal = Cat()
animal.makeNoise() // Prints "Meow".
func declareAnimalSpecies<T: Animal>(_ animal: T) {
animal.makeNoise()
print("My species is known as (T.species)")
}
let dog = Dog()
declareAnimalSpecies(dog)
// Prints:
// "Woof"
// "My species is known as Canus familiaris"
declareAnimalSpecies(animal) // <- not show error here
// error: protocol type 'Animal' cannot conform to 'Animal'...`
I’m taking example code from swift git. As I understand that animal ‘s type is any Animal so when it is passed to function declareAnimalSpecies , the compiler will infer that T is any Animal and because any Animal cannot conform to Animal so I’m expecting that it will show that error.
>Solution :
This is allowed since the implementation of SE-0352. This allows us to "open existential boxes" when calling generic methods. SE-0375 further allows us to do the same with optional parameters.
animal is an existential "box" that at runtime, holds an actual concrete type that conforms to Animal inside at. Instead of solely looking at the type constraints and saying "any Animal does not conform to Animal", the compiler is now able to see that this is a box, and in it there must be some concrete type. At runtime, it is guaranteed that there is a T that conforms to its type constraint.
As a counterexample, consider
func foo<T: Animal>(_ a: T, _ b: T) {}
func bar(_ a: any Animal, _ b: any Animal) {
foo(a, b) // error
}
This is not allowed because the existential boxes a and b might contain different concrete types at runtime, so there is not necessarily a single T type that can passed to foo.
It is important to note that, from the POV of the type system, any Animal still does not conform to Animal. If you have
struct Foo<T: Animal> {}
you still cannot write Foo<any Animal>. There are no existential boxes to open in this case.