Defining associated type in sub protocol vs generic type constraint

862 Views Asked by At

What is the difference in defining the associated types in a protocol directly that it inherits from another protocol vs using generic type constraints? For example B I get this error in TestB:

Member 'read' cannot be used on value of type 'any RepositoryB'; consider using a generic constraint instead

Could somebody explain the difference? From my understanding this should be the same as it also expects the same implementation from entities conforming to it (see RepositoryAImpl and RepositoryBImpl).

public protocol CRUDRepository {
    associatedtype Item
    associatedtype ReadInput
    
    func create(_ item: Item) async throws
    func read(_ input: ReadInput) -> AsyncThrowingStream<[Item], Error>
    func update(_ item: Item) async throws
    func delete(_ item: Item) async throws
}

public protocol RepositoryA: CRUDRepository where Item == String, ReadInput == Query {}

public protocol RepositoryB: CRUDRepository {
    associatedtype Item = String
    associatedtype ReadInput = Query
}

public struct Query {}

struct RepositoryAImpl: RepositoryA {
    func create(_ item: String) async throws {
        
    }
    
    func read(_ input: Query) -> AsyncThrowingStream<[String], Error> {
        AsyncThrowingStream { continuation in
            continuation.yield(["Test"])
        }
    }
    
    func update(_ item: String) async throws {
    }
    
    func delete(_ item: String) async throws {
    }
}

struct RepositoryBImpl: RepositoryB {
    func create(_ item: String) async throws {
    }
    
    func read(_ input: Query) -> AsyncThrowingStream<[String], Error> {
        AsyncThrowingStream { continuation in
            continuation.yield(["Test"])
        }
    }
    
    func update(_ item: String) async throws {
    }
    
    func delete(_ item: String) async throws {
    }
}

struct TestA {
    private let repository: any RepositoryA
    init(repository: any RepositoryA) {
        self.repository = repository
    }
    
    func start() -> AsyncThrowingStream<[String], Error> {
        repository.read(Query())
    }
}

struct TestB {
    private let repository: any RepositoryB
    init(repository: any RepositoryB) {
        self.repository = repository
    }
    
    func start() -> AsyncThrowingStream<[String], Error> {
        repository.read(Query())
    }
}
1

There are 1 best solutions below

0
Ranoiaetep On

The problem here is that associatedtype Item = String doesn't enforce comforming type's ConformingType.Item == String. It merely gives a default type for ConformingType.Item, and conforming type is still allowed to override it through typealias Item = ....

If you don't want to use same-type constraint via a where clause, you can use typealias instead:

public protocol RepositoryB: CRUDRepository {
    typealias Item = String
    typealias ReadInput = Query
}