Why can't I assign specific type to a protocol that uses associated type?

I had a problem, I spent 2 days refactoring my code, and suddenly I realized that I had made a huge mistake that Swift does not allow me to assign a specific type to a protocol that uses associated type such as:

protocol CollectionViewDataManager {
	associatedtype DataType: Hashable	
}
var dataManager: CollectionViewDataManager<Int> { // Protocol 'CollectionViewDataManager' can only be used as a generic constraint because it has Self or associated type requirements
	// Returns a different dataManager based on a different situation.
}

It's just so weird to me that CollectionViewDataManager needs an associatedtype and I give it a specific type, but why doesn't Swift allow it?

From my point of view, I don't think there is any difference between:

protocol CollectionViewDataManager {
	associatedtype DataType: Hashable	
}
var dataManager: CollectionViewDataManager<Int>

and

protocol CollectionViewDataManager {
	typealias DataType = Int
}
var dataManager: CollectionViewDataManager

and

struct CollectionViewDataManager<DataType>{
}
var dataManager: CollectionViewDataManager<Int>

Sadly, this just isn't working, but why? I understand why can't I simply return a protocol has associated type directly, but why can't I assign a specific type to it?

Associated types are specified by the implementer of a protocol. Generics are specified by the caller when instantiating.

An example for this is the following:

The Sequence protocol has an associated type Element.

Array implements Sequence and forwards this associated type as a generic type parameter to the caller:

struct Array<Element>: Sequence {
    typealias Element = Element // associatedtype requirement Element is fulfilled with generic type Element
}

Then, the caller can decide to use an Array<Int>, an Array<String>, etc

However, the type String also conforms to Sequence but only allows Character for Element:

struct String: Sequence {
    typealias Element = Character
}

The implementer decides what the associated type should be.

(Types have been simplified for this example)

As you can see, associatedtype and generics are not the same thing. They are different things and should be treated as such.

Swift does not have generalized existentials yet, which would bridge the gap. You may find some discussions about them here on the forum.

With those, you could write something like

var dataManager: some CollectionViewDataManager where DataType == Int

(not currently valid Swift syntax)

So as of right now, you have to consider alternatives:

  1. Make CollectionViewDataManager a class and subclass that. Concrete types can have generic parameters.

  2. Use type erasure. The Swift standard library uses this approach with types such as AnyHashable or AnySequence.
    Basically, you will have to write a wrapper type AnyCollectionViewDataManager<DataType>: CollectionViewDataManager.

struct AnyCollectionViewDataManager<DataType>: CollectionViewDataManager {
    init<Wrapped: CollectionViewDataManager>(_ wrapped: Wrapped) where Wrapped.DataType == DataType {}
}

You can then create closures in the initializer, that perform method calls to the wrapped manager.

Lastly, you can make the enclosing type generic:

struct DataManagerHolder<DataSource: CollectionViewDataManager> where DataSource.DataType == Int {
    var dataManager: DataSource
}
4 Likes

Sure, now suppose you want to store an existential that can hold any type that uses Int for the associated type. This should be possible, because all of the associated types have a known concrete type (as in the OP’s example), however Swift doesn’t allow you to express this. I think it should.

I know of this solution, but I separate over 60 functions into the protocol and 4 different manager types based on UICollectionViewDataSource/UICollectionViewDiffableDataSource so I can't use the subclass way and I think using a protocol is still the best way unfortunately....
What I mean is, although associatedtype and generics are not the same thing, but what the compiler needs are both only of one specific type.

Without knowing the details, 60 functions in one protocol does not sound like good software design.

You should probably split up the manager into different protocols. Some of them might not even require an associated type.

Granted, that does not solve the generalized existentials issue, but it is something to consider.

You are right I should do that, but it still has so many functions I have to separate....

Back to the topic, what I would guess that if Swift supports inferring protocols with associated types, then it may lead to unpredictable complexity, such as:

protocol ProtocolA {
	associatedtype TypeA
}
protocol ProtocolB: ProtocolA where TypeA == Int {
	associatedtype TypeB
}
protocol ProtocolC: ProtocolB where TypeB == String {
	
}

struct TestType: ProtocolC {
	let arrayb = [ProtocolB]() // Protocol 'ProtocolB' can only be used as a generic constraint because it has Self or associated type requirements
	let arrayc = [ProtocolC]() // Protocol 'ProtocolC' can only be used as a generic constraint because it has Self or associated type requirements
}

In this case, ProtocolC does not remain associatedtypes and TestType does not require TypeA and TypeB, they have a specific type, but Swfit still thinks it does, and that looks stupid to me

Another problem is, unlike the generics, associatedtypes in a protocol don't have a specific order, so the compiler can't know which type should be assigned to which associatedtype, may be it should write as:

var dataManager: CollectionViewDataManager<DataType: Int>

?