swiftswift-protocols

How to use a generic constraint to associate two associatedtypes in separate protocols


I've a problem with associated types in Swift, that probably result from my lack of experience. Please consider the following Code, resulting in the inlined error message:

protocol A {
  associatedtype TA
}

protocol B {
  associatedtype TB
  var a: any A { get }
}

extension A {
  func methodA(param: TA) {
    print(param)
  }
}

extension B {
  func methodB(param: TB) {
    a.methodA(param: param) // Member 'methodA' cannot be used on value of type 'any A'; consider using a generic constraint instead
  }
}

class AImpl: A {
  typealias TA = String
}

class BImpl: B {
  typealias TB = String

  var a: any A = AImpl()
}

let b = BImpl()
b.methodB(param: "Hello World")

Now my actual code was much more complex and I do understand now that the compiler of course is unable to ensure TA and TB are of the same type. I however have no idea how to solve this. The probably needs to be a where somewhere, but I got no idea where to even start with these 'generic constraints'. Any suggestions would be much appreciated.


Solution

  • You have to change your abstraction a bit if you want to ensure that the associated type of A.TA matches B.TB.

    In B, you need to declare another associatedtype, which conforms to A and declare the property a to be of this new associated type. Once you do this, you can add a where clause to the methodB function where you can refer to A.TA and make sure that it equals TB.

    protocol A {
      associatedtype TA
    }
    
    protocol B {
      associatedtype SpecificA: A
      associatedtype TB
      var a: SpecificA { get }
    }
    
    extension A {
      func methodA(param: TA) {
        print(param)
      }
    }
    
    extension B {
      func methodB(param: TB) where SpecificA.TA == TB {
        a.methodA(param: param)
      }
    }
    
    class AImpl: A {
      typealias TA = String
    }
    
    class BImpl: B {
      typealias TB = String
    
      var a = AImpl()
    }
    
    let b = BImpl()
    b.methodB(param: "Hello World")