First off, you probably know this, but both f1
and f2
should really take arguments of type some Sequence<Key>
, not any
, which would make the error go away.
As I understand it, this is a known limitation that could be lifted in the future. Let me try to break it down.
Starting with f2
: forEach
can preserve the concrete Key
type thanks to Covariant Erasure with Constrained Existentials in SE-0353: Constrained Existential Types. So this is relatively straightforward.
In the case of f1
, the compiler essentially rewrites the for
loop into a while
loop, like this:
var iterator = keys.makeIterator()
while let key = iterator.next() {
…
}
If we write this while
loop by hand, we get an error on the makeIterator()
call in the first line:
error: inferred result type 'any IteratorProtocol' requires explicit
coercion due to loss of generic requirements
The compiler forces us to write keys.makeIterator() as any IteratorProtocol
to acknowledge that the iterator
variable loses the knowledge that its Element == Key
, and this is the reason why the element type in Any
in your for
loop.
Pretty much this exact situation is described in "Losing" constraints when type-erasing resulting values in SE-0352: Implicitly Opened Existentials:
When the result of a call involving an opened existential is type-erased, it is possible that some information about the returned type cannot be expressed in an existential type, so the "upper bound" described above will lose information.
That section also mentions that future compiler versions could increase the expressivity of the language by recovering more type information that right now. It even makes explicit mention of primary associated types SE-0353 that could help with this.
In summary, it seems like the compiler could (and should, eventually) preserve the element type information when making an iterator from an any Sequence<T>
, but that hasn't been implemented yet (and if implemented, it presumably would also have to go through Swift Evolution).