skip to Main Content

I have a requirement where I have multiple struct conforming to protocol P, which I later make a collection of and execute methods like compute() via a Computer struct which helps in creating an instance of each conforming type

Why would the following code not run and what changes are required to make it run:

import Foundation

protocol P {
    static func instance() -> Self
}

struct A: P {
    static func instance() -> A {
        A()
    }
}

struct B: P {
    static func instance() -> B {
        B()
    }
}

struct Computer<T: P> {
    
    static func compute() -> T {
        T.instance()
    }
}

let someArray: [P.Type] = [
    A.self,
    B.self
]

for value in someArray {
    Computer<value>.compute() // ERROR: Cannot find type 'value' in scope
}

2

Answers


  1. You don’t need the generic constraint if you pass the type as a variable to compute like this:

    struct Computer {
        static func compute(with anyP: P.Type) -> P {
            anyP.instance()
        }
    }
    

    Alternatively, if the resulting type of compute should not be P explicitly, you could put the generic constraint into the method:

    struct Computer {
        static func compute<T: P>(with anyP: T.Type) -> T {
            anyP.instance()
        }
    }
    

    And then call compute like this:

    for value in someArray {
        let instance = Computer.compute(with: value)
        instance.doSomething()
    }
    

    This assumes that P has the method doSomething. If you want to access a method that is specific to A, you will always need to type cast. In the end instance will always be a P to the compiler, because it has to assume that the same code will be executed for any values within someArray, which is P.Type. No matter whether compute returns P or explicitly T, instance will still be inferred to be P if you place it in a loop like this.

    Login or Signup to reply.
  2. If you did not require the generic Computer struct, then you could directly extend P(which is functionally the same as @JanMensch’s answer)

    extension P {
        static func compute() -> Self {
            instance()
        }
    }
    
    let someArray: [P.Type] = [
        A.self,
        B.self
    ]
    
    for value in someArray {
        value.compute()
    }
    

    but if you needed it, I don’t think this would be possible. You’re trying to create a new type at runtime (Computer<A.Type> and Computer<B.Type>). You cannot do that. Swift Generics only help with code duplication, AFAIK.

    EDIT:

    I have another roundabout solution where the Compute<> is transient in a extension on P. However, I’m not sure if this would fit your use case without additional information, though.

    extension P {
        static func computeImpl() -> Self {
            Computer<Self>.compute()
        }
    }
    
    struct Computer<T: P> {
        static func compute() -> T {
            print(T.self)
            return T.instance()
        }
    }
    
    let someArray: [P.Type] = [
        A.self,
        B.self
    ]
    
    for value in someArray {
        value.computeImpl()
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search