Vishal Singh Vishal Singh - 1 month ago 12
Swift Question

Unable to use protocol as associatedtype in another protocol in Swift

I have a protocol,

Address
, which inherits from another protocol,
Validator
, and
Address
fulfills the
Validator
requirement in the extension.

There is another protocol,
FromRepresentable
, which has an
associatedType
(
ValueWrapper
) requirement which should be
Validator
.

Now if I try to use
Address
as
associatedType
, then it does not compile. It says,


Inferred type 'Address' (by matching requirement 'valueForDetail') is
invalid: does not conform to 'Validator'.


Is this usage illegal? Shouldn't we be able to use
Address
in place of
Validator
, as all
Addresses
are
Validator
.

Below is the piece of code I am trying.

enum ValidationResult {
case Success
case Failure(String)
}

protocol Validator {
func validate() -> ValidationResult
}

//Address inherits Validator
protocol Address: Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}

////Fulfill Validator protocol requirements in extension
extension Address {
func validate() -> ValidationResult {
if addressLine1.isEmpty {
return .Failure("Address can not be empty")
}
return .Success
}
}

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


// Shipping Address conforming to Address protocol.
// It should also implicitly conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
var addressLine1 = "CA"
var city = "HYD"
var country = "India"
}


// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
case Address1
case City
case Country

func valueForDetail(valueWrapper: Address) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
}

Answer

The problem, which David has already eluded to, is that once you constrain a protocol's associatedtype to a specific protocol, you have to use a concrete type to satisfy that requirement. This is simply a language limitation, as 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.

Therefore 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))
Comments