tobygriffin tobygriffin - 1 month ago 7
Swift Question

Getting around "Protocol X can only be used as a generic constraint"

I'm working on a form-input library. My goal is to have a re-usable set of validators which can be applied to a set of form fields. I'm running into difficulty specialising my generic protocol. The full error from the code below is

protocol 'FieldValidator' can only be used as a generic constraint because it has Self or associated type requirements
.

Complete playground-ready code:

import Foundation

protocol FieldValidator {
associatedtype InputType: Any
func validate(input value: InputType)
}

struct EmailValidator: FieldValidator {
func validate(input value: String) {}
}

enum Field {
case string(_: [FieldValidator])
case integer(_: [FieldValidator])
}

let emailField: Field = .string([EmailValidator()])


What I've tried

I understand that in the
Field
enum I can't just throw in a
FieldValidator
because it needs to know what
InputType
of validator it requires. I expect that I need to tell it somehow, maybe something like this:

case string(_: [FieldValidator<String>])
case integer(_: [FieldValidator<Int>])


or this:

case string(_: [FieldValidator where InputType == String])
case integer(_: [FieldValidator where InputType == Int])


but these doesn't work. Is there a way to keep this kind of architecture?

Edit using
struct
instead of
enum
for field types:


struct StringField {
typealias InputType = String
let validators: [FieldValidator]
}


I still appear to have the same problem defining the set of validators (which must be provided when the Field is initialised):
protocol 'FieldValidator' can only be used as a generic constraint because it has Self or associated type requirements
.

Answer

I suppose what I'm trying to do is provide a mechanism by which someone can define a Field, define what type of value it holds, and define a set of reusable Validators which will operate on that value and determine if it's valid

You might be after something like this; it's stupid but effective, especially if there are not very many field types in question:

protocol FieldValidator {
    associatedtype T
    func validate(input:T)
}

class StringValidator : FieldValidator {
    func validate(input:String) { fatalError("must override me") }
}

class IntValidator : FieldValidator {
    func validate(input:Int) { fatalError("must override me") }
}

class ActualStringValidator : StringValidator {
    override func validate(input:String) { print(input)}
}

enum Field {
    case string([StringValidator])
    case int([IntValidator])
}

As you can see, I've simply used the class hierarchy to solve the problem (so that we don't have to do type erasure). In particular, it is now legal to say:

let f = Field.string([ActualStringValidator()])

Here's how to test it:

let f = Field.string([ActualStringValidator()])
if case Field.string(let arr) = f {
    for thing in arr {
        thing.validate(input:"howdy")
    }
}
Comments