Search code examples
swiftgenericsprotocolsassociated-types

Use Protocol with associatedtype as a Type


I have the protocol like this

protocol ViewBuilder {
    associatedtype In
    associatedtype Out
    
    func build(_ data: In?) -> Out?
}

I have the concrete struct like these. TitleTypeView and ButtonTypeView have the same super class.

struct HeaderTitleBuilder: ViewBuilder {
    func build(_ data: DataSource?) -> ViewFactory? {
        guard let data = data else { return nil }
        return TitleTypeView(data)
    }
}

struct HeaderButtonBuilder: ViewBuilder {
    func build(_ data: DataSource?) -> ViewFactory? {
        guard let data = data else { return nil }
        return ButtonTypeView(data)
    }
}

I have to combine the Builders struct into HeaderBuilder

struct TestStruct {
    func create() {
        HeaderBuilder(titleBuilder: HeaderTitleBuilder()
                                           , seeallBuilder: HeaderButtonBuilder())
    }
}

struct HeaderBuilder
{
    private(set) var titleBuilder: some ViewBuilder = HeaderTitleBuilder()
    private(set) var buttonBuilder: some ViewBuilder = HeaderButtonBuilder()
}

I got this error

Cannot convert value of type 'HeaderTitleBuilder' to expected argument type 'some ViewBuilder'

Cannot convert value of type 'HeaderButtonBuilder' to expected argument type 'some ViewBuilder'

So I try to use protocol as a generic constraint

struct HeaderBuilder <T: ViewBuilder>
where T.In == DataSource, T.Out == ViewFactory {

    private(set) var titleBuilder: T = HeaderTitleBuilder()
    private(set) var buttonBuilder: T = HeaderButtonBuilder()
}

DataSource and ViewFactory are all protocols without associatedtype.

I got this error.

Conflicting arguments to generic parameter 'T' ('HeaderTitleBuilder' vs. 'HeaderButtonBuilder')

I guess the compiler is using HeaderTitleBuilder and HeaderButtonBuilder to infer the type of T. But it is not what I want. I just need to T to be a protocol type that allows me to pass any Builder type.


Solution

  • Since ViewBuilder is a protocol, when you try make T a concrete type, it creates a conflict whether that type T is HeaderTitleBuilder or HeaderButtonBuilder. T cannot be both at the same time.

    You can instead make both have a separate generic parameter, e.g,:

    struct HeaderBuilder<TitleBuilder: ViewBuilder, ButtonBuilder: ViewBuilder> where TitleBuilder.In == DataSource, TitleBuilder.Out == ViewFactory, ButtonBuilder.In == DataSource, ButtonBuilder.Out == ViewFactory {
        private(set) var titleBuilder: TitleBuilder
        private(set) var buttonBuilder: ButtonBuilder
    }