3
votes

I'd like to declare generic protocols similar to the following:

protocol Factory {
    func createWidget<T, TWidget>(_ t: T) -> TWidget 
    where TWidget: Widget, TWidget.T == T
}

protocol Widget {
    associatedtype T
    func get() -> T
}

I'm hoping that I can implement concrete variations of Factory returning their own concrete and opaque Widget with a hidden implementation.

Here's an example implementation that fails to build:

struct ConcreteFactory: Factory {
    func createWidget<T, TWidget>(_ t: T) -> TWidget 
    where TWidget: Widget, TWidget.T == T {
        // This line has an error…
        return ConcreteWidget(widgetValue: t)
    }
}

struct ConcreteWidget<T>: Widget {
    let widgetValue: T

    init(widgetValue: T) {
        self.widgetValue = widgetValue
    }

    func get() -> T {
        return widgetValue
    }
}

However, this does not compile.

At the line indicated, Swift's compiler give the error "Cannot convert return expression of type 'ConcreteWidget' to return type 'TWidget'".

I also tried having ConcreteFactory return a ConcreteWidget, but then the error is that ConcreteFactory doesn't conform to Factory.

1

1 Answers

4
votes

This can’t work. When you call your createWidget method you specify two types T and TWidget.

struct MyWidget: Widget { 
   func get() -> Int { ... }
}

let widget: MyWidget = factory.createWidget(12)

In this Example TWidget is MyWidget and T is Int. And this shows nicely why your approach can’t work. You cannot assign a ConcreteWidget<Int> to a variable of type MyWidget.

What you need is a type-eraser for your widgets. Currently you have to write that yourself, but in the future the compiler will hopefully generate them automatically when needed.

struct AnyWidget<T>: Widget {
    private let _get: () -> T

    init<Other: Widget>(_ other: Other) where Other.T == T {
        _get = other.get
    }

    func get() -> T {
        return _get()
    }
}

This allows you to write your factory protocol and implementation:

protocol Factory {
    func createWidget<T>(_ t: T) -> AnyWidget<T>
}

struct ConcreteFactory: Factory {
    func createWidget<T>(_ t: T) -> AnyWidget<T> {
            return AnyWidget(ConcreteWidget(widgetValue: t))
    }
}