Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I conform a Swift enum to `Equatable` when it has an any existential as one of its associated values?

Suppose I have:

protocol MyError: Error, Equatable {
  var errorDispalyTitle: String { get }
  var errorDisplayMessage: String { get }
}

enum ContentState {
  case .loading
  case .error(any MyError)
  case .contentLoaded
}

If i were to implement Equatable in ContentState so I can compare during unit tests I end up with a problem because I have to compare two any MyError types which are boxed and might be of two different underlying types.

extension ContentState: Equatable {
  static func == (lhs: ContentState, rhs: ContentState) -> Bool {
    switch (lhs, rhs) {
    case (.loading, .loading):
      return true
    case (.contentLoaded, .contentLoaded):
      return true
    case (.error(let lhsError), .error(let rhsError)):
      // TODO: have to compare if both underlying types are match and then if they are equal in value
    default:
      return false
    }
  }
}

How do I do this?

I tried lifting the generic from the existential type there to the type (e.g. ContentState<Error: MyError>) which lets it compile when implementing equatable as it knows how to infer the type, but the problem is for whichever class uses that enum it doesnt matter which type is receiving of it, only that is any type of it, and if I don't implement the any existential it starts requiring the generic to be propagated up the chain.

like image 580
corujautx Avatar asked Dec 07 '25 06:12

corujautx


1 Answers

You can write a wrapper that wraps an Equatable, and wrap the LHS and RHS errors before comparing.

// resembling a similar behaviour to AnyHashable...
class AnyEquatable: Equatable {
    let value: Any
    private let equals: (Any) -> Bool

    init<E: Equatable>(_ value: E) {
        self.value = value
        self.equals = { type(of: $0) == type(of: value) && $0 as? E == value }
    }

    static func == (lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
        lhs.equals(rhs.value)
    }
}

Then in your switch you can do:

case (.error(let lhsError), .error(let rhsError)):
  return AnyEquatable(lhsError) == AnyEquatable(rhsError)

Note that if MyError inherits from Hashable instead of Equatable, you can use the builtin AnyHashable instead of writing your own AnyEquatable.

like image 88
Sweeper Avatar answered Dec 09 '25 19:12

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!