Conform Never to Equatable and Hashable

I've found Never very helpful when working with generic types. But its usefulness is limited by the fact that it doesn't conform to Equatable and Hashable. I'd like to add those conformances to the standard library.

Consider the following result type:

enum Result<Value, Error> {
  case success(Value)
  case failure(Error)
}

extension Result: Equatable where Value: Equatable, Error: Equatable {
  static func == (lhs: Result, rhs: Result) -> Bool {
    switch (lhs, rhs) {
    case let (.success(lhs), .success(rhs)):
      return lhs == rhs
    case let (.failure(lhs), .failure(rhs)):
      return lhs == rhs
    case (.success, .failure), (.failure, .success):
      return false
    }
  }
}

extension Result: Hashable where Value: Hashable, Error: Hashable {
  var hashValue: Int {
    switch self {
    case let .success(value):
      return value.hashValue
    case let .failure(error):
      return error.hashValue
    }
  }
}

The conditional conformances to Equatable and Hashable can be very useful for testing results or putting them in collections.

It's often also convenient to use Never to represent something that always succeeds (Result<_, Never>) or always fails (Result<Never, _>).

Unfortunately, those don't play well together. Either (1) Result needs to add 2 more Equatable and Hashable conformances for when values or errors are Never or (2) Never itself needs to be Equatable and Hashable.

This isn't unique to Result. I've run into this with other enums and even with classes that are generic over optional values.

Writing these conformances for Never is easy:

extension Never: Equatable {
  public static func == (lhs: Never, rhs: Never) -> Bool {
    switch (lhs, rhs) {
    }
  }
}

extension Never: Hashable {
  public func hashValue: Int {
    switch self {
    }
  }
}

But adding them yourself is problematic—especially if you're writing code in a shared library. So I'd like to add these conformances to the standard library.

(Ideally, these implementations could be synthesized by the compiler. But that's not possible today.)

15 Likes

Never could actually conform to any protocol without associated types and static requirements, am I right?

What would be the practical difference between Result<_, Never> and Result<_, Void>? Does one of these make more sense than the other? Because this is very close to my question about equality for generic containers. (Where the problem with protocol conformances is similar.)

It's not about making more or less sense. They mean different things. :slight_smile:

Never is an uninhabitable type—it can't be constructed. So a Result<_, Never> can provably never fail. The failure case literally cannot be constructed.

Void can be instantiated. It has a single value: (). So a Result<_, Void> can fail, but it can only fail in one way. This can be useful if you want to know whether something failed, but you don't care why it failed.

12 Likes

I am very interested to see more compiler support for bottom types in general. A bottom type like Never should indeed conform to any protocol including equatable and hashable.

1 Like

Out of curiosity, do you see Never as always being equal or unequal to another Never value?

There’s no such thing as “a Never value”—you can’t create one. That’s the point of the type. So the question doesn’t make sense. :slight_smile:

6 Likes

Thanks for the pedantry.

What is the result of the following expression supposed to be?

let a: Never
let b: Never

a == b

That would be error: constant 'a' used before being initialized? In this case I don’t find Never different from Int, for example.

2 Likes
extension Never: Equatable {
    public static func ==(lhs: Never, rhs: Never) -> Bool {
        return true
    }
}

extension Never: Equatable {
    public static func ==(lhs: Never, rhs: Never) -> Bool {
        return false
    }
}

Which implementation is intended? Will it ever matter?

Neither. I gave the implementation above:

extension Never: Equatable {
  public static func == (lhs: Never, rhs: Never) -> Bool {
    switch (lhs, rhs) {
    }
  }
}
2 Likes

I am beginning to understand. Why doesn't the compiler complain of no value being returned?

That implementation won't compile because the function doesn't return a value.

The proper implementation, IMO, is this:

public static func == (lhs: Never, rhs: Never) -> Bool {
  fatalError("should never be called")
}

Because it is not possible to construct a Never value and neither true nor false is the correct answer here.

EDIT: Looks like I'm wrong about this! It's interesting to see that the compiler detects this properly.

1 Like

It does compile. A value is returned from every case of the switch.

13 Likes

IMO the compiler should be smart enough to know when a body of code can never execute because it requires an instance of Never. If it were, the implementation would just be:

public static func == (lhs: Never, rhs: Never) -> Bool {}
6 Likes

I suppose I should have tried it first. Interesting! I'm happy to be wrong about that. :slight_smile:

1 Like

Similarly, having Void be Equatable/Hashable/Decodable/etc could be very helpful as well, unfortunately there is no language support for this yet.

5 Likes

If you find this interesting and would like to know more, I highly recommend @mbrandonw and @stephencelis’s Point Free screencast. :sparkling_heart:

1 Like

This would fall out naturally if tuples automatically conformed to Equatable/Hashable when their elements do, since Void is a typealias for () and the empty tuple is trivially equal to itself and could have any arbitrary hash value.

1 Like
extension Never: Equatable {
    public static func ==(lhs: Never, rhs: Never) -> Bool {
        switch (lhs, rhs) {
        }
    }
}

enum E<T> {
    case e (T)
}

extension E: Equatable where T: Equatable {
    static func == (lhs: E, rhs: E) -> Bool {
        switch (lhs, rhs) {
        case let (.e(lhs), .e(rhs)): return lhs == rhs
        }
    }
}

let e = E<Never>.e
let f = E<Never>.e

e == f // error: binary operator '==' cannot be applied to two '(Never) -> E<Never>' operands

While I understand the error, I would hope the compiler would get a better error message for this case. There's nothing here that indicates the reason why two Never operands can't be compared, even though they are Equatable.