Search code examples
swiftgenericsassociated-typesgeneric-associated-types

How to use associatedtype from the generic as a type?


I have this sort of code:

protocol CoreTypes {
    associatedtype TypeA
    // ...more types and various operations between them
}

protocol Context<Types> {
    associatedtype Types: CoreTypes

    var valueA: Types.TypeA { get }
    func setValueA(_ b: Types.TypeA)
}

class Operation<Types: CoreTypes> {
    var context: any Context<Types>

    init(context: some Context<Types>) {
        self.context = context
    }

    func perform() {
        context.setValueA(context.valueA)
    }
}

However, context.setValueA(context.valueA) doesn't compile. The error is:

Cannot convert value of type 'Any' to expected argument type 'Types.TypeA'

It's like Swift doesn't understand that these two definitions use the same type:

var valueA: Types.TypeA { get }
func setValueA(_ b: Types.TypeA)

I know that I could fix this specific issue by defining Context<TypeA>, but I need to use it indirectly, i.e. from Types.TypeA. Is there a way to make this work?


It also works fine if I convert Context to a class, i.e.:

class Context<Types: CoreTypes> {
    var valueA: Types.TypeA { preconditionFailure() }
    func setValueA(_ b: Types.TypeA) { preconditionFailure() }
} 

as that way I can drop any from var context: Context<Types>, but using these "fake abstract classes" makes things less compile time safe...

So is there a better way to use associatedtype from another type?


Solution

  • You can write generic helper function to open up the existential type (SE-0352):

    func perform() {
        func helper<T: Context<Types>>(_ x: T) {
            x.setValueA(x.valueA)
        }
        helper(context)
    }
    

    That said, from my understanding of SE-0353, context.setValueA(context.valueA) should also be possible, since the associated type of Context is constrained to a concrete type.

    context.setValueA(context.valueA) compiles if it were:

    var valueA: Types { get }
    func setValueA(_ b: Types)
    

    So it is possible that the Swift team didn't think of this case when designing/implementing SE-0353.