skip to Main Content

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
}

2

Answers


  1. 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
        }
    }
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search