Any + Protocol + associated type

I have an element which I want to store in a tree.

protocol GenericElement<Key, Value>
{
	associatedtype Key
	associatedtype Value
}

The tree consists of nodes and leafs which adhere to common protocol.

protocol Page<Element>
{
	associatedtype Element : GenericElement

	func fetch(for: Element.Key) -> Element?
}

struct Node<Element> : Page
where Element : GenericElement
{
	func fetch(for: Element.Key) -> Element? { nil }
}

struct Leaf<Element> : Page
where Element : GenericElement
{
	func fetch(for: Element.Key) -> Element? { nil }
}

The tree has a root which is either a node or a leaf.

struct Tree<Element>
where Element : GenericElement
{
	var root: any Page<Element>
}

I want to fetch an element from the tree.

extension Tree
{
	func fetch(for key: Element.Key) -> Element? { root.fetch(for: key) }
}

Unfortunately this does not compile:

Member 'fetch' cannot be used on value of type 'any Page<Element>'; 
consider using a generic constraint instead

This however does compile:

protocol Page<Element, Key>
{
	associatedtype Element : GenericElement where Element.Key == Key
	associatedtype Key

	func fetch(for: Key) -> Element?
}

with root being:

var root: any Page<Element, Element.Key>

The same Element with the same associated Key is used everywhere.
I kinda expected that passing along Element would be sufficient for the compiler to know about Key.

If somebody could explain to me why my expectation was wrong, it would be much appreciated.
It seems to me I am just passing along duplicate information in the second version.
This was such a time sink I really want to avoid my mistake/misunderstanding in the future.
Thank you for reading.

1 Like

Maybe coz type erasure is safe to perform only in covariant position.

Implicitly opened existentials still need work; they currently often require writing Swift 1.x-style code, employing generic functions instead of protocol extensions.

You can solve your issue by switching the body of Tree.fetch to this:

func fetch(_ page: some Page<Element>) -> Element? {
  page.fetch(for: key)
}
return fetch(root)

…or, preferably, you could get rid of the icky existential.

struct Tree<Page: Module.Page> {
  typealias Element = Page.Element
  var root: Page
}
1 Like