编译器错误:将`associatedtype`值从一个协议传递到另一个协议。

huangapple go评论106阅读模式
英文:

Compiler Error Passing `associatedtype` value from one Protocol to another

问题

protocol Valuable {
    associatedtype ValueType
    
    var value: ValueType? { get }
    var rules: [AnyValidatable<Self>] { get }
    
    mutating func update(_ newValue: ValueType) throws
}

protocol Validatable {
    associatedtype T
    func validate(_ value: T) throws
}

struct AnyValidatable<V>: Validatable {
    typealias T = V.ValueType
    private let _validate: (T) throws -> Void
    
    init<U: Validatable>(_ validatable: U) where U.T == T {
        _validate = validatable.validate
    }
    
    func validate(_ value: T) throws {
        try _validate(value)
    }
}

struct StringValue: Valuable {
    typealias ValueType = String
    var value: String?
    var rules: [AnyValidatable<StringValue>]
    
    mutating func update(_ newValue: String) throws {
        let errors = rules.compactMap { rule in
            do {
                try rule.validate(newValue)
                return nil
            } catch {
                return error
            }
        }
        
        if errors.isEmpty {
            self.value = newValue
        } else {
            throw ValidationError.Invalid(subErrors: errors)
        }
    }
}
英文:

I'm working in Swift 5.7. I need to implement what's essentially form validation but with a twist. Instead of evaluating rules and returning boolean values, I need to throw an error upon failure.

I have the following protocol, which can be added to but otherwise not changed:

protocol Valuable {
    associatedtype ValueType
    
    var value: ValueType? { get }
    
    mutating func update(_ newValue: ValueType) throws
}

I'm attempting to add an array of rules, conforming to a different protocol, which need to pass in order for the new value to be saved. I have two problems which I'm facing:

  • I need to constrain the second protocol to have the same associated type as Valuable
  • I'm getting a compiler error when I attempt to pass value as a parameter to a function in the second protocol: Member &#39;validate&#39; cannot be used on a value of type &#39;any Validatable&#39;; consider using a generic constraint instead

My understanding of this compiler error (which may be a faulty understanding) is that because any Validatable is being consumed, Swift is unable to unbox and/or infer the underlying type. How do I get around this?

For enforcing the same associatedtype, I tried adding associatedtype Rule: Validatable where Rule.T == ValueType to the Valuable protocol, but then the compiler complains that my struct does not conform to Valuable. I looked at other posts regarding constraining protocol types, but it appears that those recommendations either no longer work with Swift 5.7, or are missing something.

Here's a simplified example of my code:

enum ValidationError: Error {
    case Invalid(subErrors: [Error])
}

protocol Valuable {
    associatedtype ValueType
    
    var value: ValueType? { get }
    var rules: [any Validatable] { get }
    
    mutating func update(_ newValue: ValueType) throws
}

protocol Validatable {
    associatedtype T
    func validate(_ value: T) throws
}

struct StringValue: Valuable {
    typealias ValueType = String
    var value: String?
    var rules: [any Validatable]
    
    mutating func update(_ newValue: String) throws {
        let errors = rules.compactMap { rule in
            do {
                try rule.validate(newValue) // &lt;~~ Compiler error here
                return nil
            } catch {
                return error
            }
        }
        
        if errors.isEmpty {
            self.value = newValue
        } else {
            throw ValidationError.Invalid(subErrors: errors)
        }
    }
}

答案1

得分: 1

Add a primary associate type to Validatable:

protocol Validatable<T> {
    associatedtype T
    func validate(_ value: T) throws
}

so that you can easily specify that "Validatable where T is ValueType" should be the rules array element type.

// in Valuable
var rules: [any Validatable<ValueType>] { get }

// in StringValue
var rules: [any Validatable<String>]

The compiler also seems to be unable to infer the result type of compactMap, so you should help it out:

let errors: [Error] = rules.compactMap { rule in
          ^^^^^^^^^

Also consider adding a primary associated for Valuable too. Judging from the protocol names, this seems appropriate and probably will be useful later.

protocol Valuable<ValueType> {
英文:

Add a primary associate type to Validatable:

protocol Validatable&lt;T&gt; { // add a &lt;T&gt; here
    associatedtype T
    func validate(_ value: T) throws
}

so that you can easily specify that "Validatable where T is ValueType" should be the rules array element type.

// in Valuable
var rules: [any Validatable&lt;ValueType&gt;] { get }

// in StringValue
var rules: [any Validatable&lt;String&gt;]

The compiler also seems to be unable to infer the result type of compactMap, so you should help it out:

let errors: [Error] = rules.compactMap { rule in
          ^^^^^^^^^

Also consider adding a primary associated for Valuable too. Judging from the protocol names, this seems appropriate and probably will be useful later.

protocol Valuable&lt;ValueType&gt; {

huangapple
  • 本文由 发表于 2023年5月7日 08:33:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76191762.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定