Unable to use protocol as associatedtype in another protocol in Swift

The problem, which David has already alluded to, is that once you constrain a protocol’s associatedtype to a specific (non @objc) protocol, you have to use a concrete type to satisfy that requirement.

This is because protocols don’t conform to themselves – therefore meaning that you cannot use Address to satisfy the protocol’s associated type requirement of a type that conforms to Validator, as Address is not a type that conforms to Validator.

As I demonstrate in my answer here, consider the counter-example of:

protocol Validator {
    init()
}
protocol Address : Validator {}

protocol FormRepresentable {
    associatedtype ValueWrapper: Validator
}

extension FormRepresentable {
    static func foo() {
        // if ValueWrapper were allowed to be an Address or Validator,
        // what instance should we be constructing here?
        // we cannot create an instance of a protocol.
        print(ValueWrapper.init())
    }
}

// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
    typealias ValueWrapper = Address
}

The simplest solution would be to ditch the Validator protocol constraint on your ValueWrapper associated type, allowing you to use an abstract type in the method argument.

protocol FormRepresentable {
    associatedtype ValueWrapper
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: Address) -> String {
        // ...
    }
}

If you need the associated type constraint, and each AddressFrom instance only expects a single concrete implementation of Address as an input – you could use generics in order for your AddressFrom to be initialised with a given concrete type of address to be used in your method.

protocol FormRepresentable {
    associatedtype ValueWrapper : Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom<T : Address> : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: T) -> String {
        // ...
    }
}

// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.Address1

However, if you require both the associated type constraint and each AddressFrom instance must be able to handle an input of any type of Address – you’ll have use a type erasure in order to wrap an arbitrary Address in a concrete type.

protocol FormRepresentable {
    associatedtype ValueWrapper : Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

struct AnyAddress : Address {

    private var _base: Address

    var addressLine1: String {
        get {return _base.addressLine1}
        set {_base.addressLine1 = newValue}
    }
    var country: String {
        get {return _base.country}
        set {_base.country = newValue}
    }
    var city: String {
        get {return _base.city}
        set {_base.city = newValue}
    }

    init(_ base: Address) {
        _base = base
    }
}

enum AddressFrom : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: AnyAddress) -> String {
        // ...
    }
}

let addressFrom = AddressFrom.Address1

let address = ShippingAddress(addressLine1: "", city: "", country: "")

addressFrom.valueForDetail(AnyAddress(address))

Leave a Comment