tobygriffin tobygriffin - 2 months ago 11
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
enum I can't just throw in a
because it needs to know what
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
instead of
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


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 {