Home > Software design >  Swift generics in protocols
Swift generics in protocols

Time:02-18

What is an appropriate way to solve the following example with swift?

I know because of type erasure that you cannot assign StructB to item2 in the constructor. In other languages like Java I would solve the problem by not having a generic class parameter T in the Container struct but let vars item and item2 just be the type of ProtocolA (like var item2: ProtocolA). This is not allowed by swift: Protocol 'ProtocolA' can only be used as a generic constraint because it has Self or associated type requirements

I worked through this section of swifts documentation about generics but so far I was not able to adapt a solution for my simple problem:

protocol ProtocolA {
    associatedtype B
    
    func foo() -> B
}


struct StructA: ProtocolA {
    typealias B = String
    
    func foo() -> String {
        "Hello"
    }
}

struct StructB: ProtocolA {
    typealias B = Double
    
    func foo() -> Double {
        0.0
    }
}

struct Container<T: ProtocolA> {
    
    var item: T
    var item2: T
    
    init(item: T) {
        self.item = item
        self.item2 = StructB() // err: Cannot assign value of type 'StructB' to type 'T'
    }
}

I also want to know how a solution could look if I do not want to specify a generic type parameter at all for the container struct. So that I could write the following:

func test() {
    let container = Container(StructA()) // no type given
}

CodePudding user response:

The only way to do something like you want is :

struct Container<T: ProtocolA,U: ProtocolA> {

    var item: T
    var item2: U

    init(item: T, item2: U) {
        self.item = item
        self.item2 = item2
    }
}

CodePudding user response:

For the problem as described, the answer is very straightforward:

struct Container<T: ProtocolA> {

    var item: T
    var item2: StructB // This isn't T; you hard-coded it to StructB in init

    init(item: T) {
        self.item = item
        self.item2 = StructB()
    }
}

let container = Container(item: StructA()) // works as described

I get the feeling that there's something deeper to this question, which is why Alexander was asking for more context. As written, the question's answer seems too simple, so some of us assume that there's more that you didn't explain, and we want to avoid a round of "here's the answer" followed by "that's not what I meant."

As you wrote it, you try to force item and item2 to have the same type. That's obviously not what you mean because T is given by the caller, and item2 is always StructB (which does not have to equal T, as the error explains).

Is there another way you intend to call this code? It's unclear what code that calls foo() would look like. Do you have some other way you intend to initialize Container that you didn't explain in your question?

Maybe you want two different types, and you just want StructB to be the default? In that case, it would look like (based on Ptit Xav's answer):

struct Container<T: ProtocolA, U: ProtocolA> {

    var item: T
    var item2: U

    // Regular init where the caller decides T and U
    init(item: T, item2: U) {
        self.item = item
        self.item2 = item2
    }

    // Convenience where the caller just decides T and U is default
    init(item: T) where U == StructB {
       self.init(item: item, item2: StructB())
    }
}

The where U == StructB allows you to make a default type for U.

  • Related