6
votes

I have a protocol my swift code base I have protocol with an associated type and two methods. Both of the methods define different generic constrains for the associated type of the protocol. And I would like to make the struct conform two the protocol but with two different associated types.

protocol Convertable {
    associatedtype TargetType
    func convert() -> TargetType
}

func show<T : Convertable where T.TargetType == String>(toShow : T) {
    print(toShow.convert())
}
func add<T : Convertable where T.TargetType == Int>(a : T, b : T) -> Int {
    return a.convert() + b.convert()
}

struct MyData {
    var data : Int
}

As an extension I make the struct conform the protocol where the TargetType will be String in order to pass it to the show method:

extension MyData : Convertable {
    func convert() -> String { return String(self.data) }
}

So far everything works as expected. But now I also like to have the struct to conform to the Convertable protocol when TargetType is bound to an Int. Which seems to be impossible?

The first thing I tried was to add a second definition of the convert method to the extension:

extension MyData : Convertable {
    func convert() -> String { return String(self.data) }
    func convert() -> Int { return data }
}

The compiler now complains that MyData does no longer conform to the protocol. Second was to split this into two extensions and bind the TargetType explicitly.

extension MyData : Convertable {
    typealias TargetType = Int
    func convert() -> Int { return data }
}
extension MyData : Convertable {
    typealias TargetType = String
    func convert() -> String { return String(data) }
}

This has the effect that the compiler now complains the the TargetType was redefined.

My last try was to define two protocols that extend the Convertable protocol and constrain the TargetType and then implement both of them via extension:

protocol ConvertableString : Convertable {
    associatedtype TargetType = String
}
protocol ConvertableInt : Convertable {
    associatedtype TargetType = Int
}

extension MyData : ConvertableInt {
    func convert() -> Int { return self.data }
}
extension MyData : ConvertableString {
    func convert() -> String { return String(self.data) }
}

Which now makes the compiler happy for the extensions but no longer for the call to show because it doesn’t know that it can call the function with MyData.

Is there some thing that I have overseen or is this currently not possible in swift?

1
It's not only currently not possible, but hardly will be ever possible, in my opinion. You've declared a protocol with one single associated type. How it can be set to two different types at the same type?!werediver
Well it's not the same protocol, since the protocol is generic over TargetType there are as many variants of the protocol as there for TargetType. The whole Idea of having types associated with protocols is that you can differentiate by their associated type. If you look at C# there it's possible to implement the same interface with different types bound to the generic parameter.Kolja
Well it's not C# and you should not think of it as it would be, because that just not works.werediver
What do you mean by "no longer for the call to show"? This works in my playground: let myInt: Int = myData.convert() and let myString: String = myData.convert(). (Type need to be explicitly specified since there are two candidates. Or myData.convert() as Int if return value is not needed.)Franklin Yu

1 Answers

0
votes

I just fund a way to archive this. The trick is to add another associated type in one of the subtypes of the protocol:

protocol ConvertableInt : Convertable {
    associatedtype TResI
    typealias TargetType = TResI
}

extension MyData : Convertable {
    typealias TargetType = String
    func convert() -> String { return String(self.data) }
}

extension MyData : ConvertableInt {
    typealias TResI = Int
    func convert() -> TResI { return self.data }
}

This also allows to get rid of the second subtype for string.

While this passes the compiler it totally crashes at runtime!

The compiler always calls the method defined which was defined at the explicit typealias. In this case:

typealias TargetType = String

Which will result in interpreting the address as a integer and give you totally wrong results. If you define it vice versa it will simply crash because it tries to interpret the integer as a address.