Can't conform to protocol using an existential in an associated value instead of a type erasure. Why?

103 Views Asked by At

The following code will throw an error - "Type 'SomeEnum' does not conform to protocol 'Equatable'" (and Hashable) - even though SomeProtocol conforms to Hashable and Equatable. Why?

One way to fix this is erasing the type to AnySomeProtocol, but I'm curious why is this a compiler error. I'd rather not erase the type when the existential seems to me the most expressive way to go about this.

import Foundation

enum SomeEnum: Hashable, Equatable {
    case foo
    case associatedFoo(any SomeProtocol)
}


protocol SomeProtocol: Hashable, Equatable {
    var field: String { get }
}

struct SomeDataType: Hashable, Equatable {
    let field: String
    let anotherField: Int
}

extension SomeDataType: SomeProtocol {}
1

There are 1 best solutions below

0
AudioBubble On

Type erasure won't actually help. Adding to the question how you think it would, would probably rubber duckily answer the question for yourself.

You've got to have the associated types match, in order for them to be Equatable.

enum SomeEnum<AssociatedFoo: SomeProtocol>: Hashable {
  case foo
  case associatedFoo(AssociatedFoo)
}

You can implement this yourself, given the definition you provided instead, but it won't be synthesized in Swift 5.8 and there's been no talk of it changing that I know of.

extension SomeEnum: Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool {
    switch (lhs, rhs) {
    case (.foo, .foo): return true
    case (.associatedFoo(let foo0), .associatedFoo(let foo1)): return foo0.equals(foo1)
    default: return false
    }
  }
}

public extension Equatable {
  /// Equate with a value of unknown type.
  func equals(_ any: some Any) -> Bool {
    self == any as? Self
  }
}
extension SomeEnum: Hashable {
  func hash(into hasher: inout Hasher) {
    switch self {
    case .foo:
      hasher.combine(0)
    case .associatedFoo(let foo):
      hasher.combine(1)
      hasher.combine(foo)
    }
  }
}