Derive Equatable & Hashable for uninhabited types?

Currently, an enum with no cases cannot synthesize conformance to Equatable and Hashable:

// This does not compile
enum NoCases: Hashable { }

// This does compile:
enum OneCase: Hashable {
  case one
}

I would like to change this and I've opened a PR to do so.

The current limitation is artificial. It is possible to have the compiler synthesize the conformance in this case. There's no technical reason why it shouldn't. (If you're wondering what that looks like, please read through the pitch thread for SE-0215).

However, the original proposal to synthesize Hashable and Equatable implementations said this:

The compiler does not synthesize P's requirements for an enum with no cases because it is not possible to create instances of such types.

But the inability to create instance of these types doesn't mean that the conformance isn't helpful or even sometimes necessary. I case that I'm frequently running into is where the type is used to fulfill a type requirement with generics, conditional conformance, or protocols (see code sample at the end of this post).

However, the original proposal does say that the compiler won't do this. What is required from an evolution perspective here? Does this require a separate proposal? Should the original proposal be amended in some way?

// Something approximating The Elm Architecture
// We'd like to constraint `Message` and `Action` to be `Equatable` for
// testing reasons.
protocol Component {
  associatedtype Message: Equatable

  mutating func update(_: Message) -> Command?
  nonmutating func render() -> UI<Message>
}

enum UI<Message: Equatable>: Equatable {
  case button(String, Message)
  case text(String)
}

struct MyComponent: Component {
  // This component doesn't actually send any messages, but it might someday.
  // The message has to be `Equatable` because:
  //  1. It's required by `Component.Message` (associatedtype requirement)
  //  2. It's required by `UI.Message` (generic requirement)
  enum Message: Equatable {}
  
  mutating func update(_ message: Message) -> Command? {
    switch message { }
  }
  nonmutating func render() -> UI<Message> {
    return UI.text("Hello, World!")
  }
}
6 Likes

IMO this is an obvious extension of the existing extension mechanism, and I don't think it needs a full proposal cycle.

15 Likes

Just to chime in as the author and implementor of SE-0185: I don't have any objection to this. The limitation seemed "natural" at the time and I don't recall anyone bringing up scenarios where it could be useful such as yours—that's most likely because people were already used to providing manual implementations. Now that usage has evolved, situations where synthesis doesn't fall out naturally become more aggravating today than they would have been before SE-0185. Thanks for taking it on!

13 Likes

+1, this seems like a bugfix / oversight.