---
title: 'Non-equatable void'
date: '2023-04-27'
tags: ['swift']
draft: false
summary: 'A workaround for non-nominal types that don’t conform to Equatable, but really should'
---

In Swift, we can use the `Equatable` protocol to compare two values for equality. For example, we can compare two `String`s:

```swift
let a = "Hello"
let b = "Hello"
a == b // true
```

We can also compare two of any struct or class that conforms to `Equatable`:

```swift
struct Person: Equatable {
    let name: String
    let age: Int
}

let a = Person(name: "John", age: 42)
let b = Person(name: "John", age: 42)
a == b // true
```

Additionally, many types conform to equatable based on conditional generic conformances, such as optional:

```swift
let a: String? = "Hello"
let b: String? = "Hello"
a == b // true
```

In Swift, though, non-nominal types (that is, types that don't have a name) cannot conform to protocols.
There are a few non-nominal types that come to mind:
 - Functions, such as `(Int) -> Void`
 - Closures, such as `{ (Int) -> Void in }`
 - _Tuples, such as `(Int, String)`_
    - `Void` (uh oh)
 - Metatypes, such as `Int.Type`
 - Existentials, such as `Sendable & AnyObject`

The most commonly used type here is the tuple type. These are usually used as shorthand for a plain data structure,
and it can be surprising that they don't conform to `Equatable`:

```swift
let a = (1, "Hello")
let b = (1, "Hello")
a == b // error: binary operator '==' cannot be applied to two '(Int, String)' operands
```

`Void` falls into this category. The definition of void in swift is `typealias Void = ()`, so it's a tuple with no elements.
You [can compare two voids](https://nshipster.com/void/#:~:text=Void%20is%20an%20empty%20tuple,properties%20or%20conformance%20to%20protocols.&text=Void%20doesn't%20conform%20to,%E2%80%94%20it%20simply%20can't.),
but using Void in a struct will prevent automatic equatable conformance:

```swift
struct Person: Equatable { // error: type 'Person' does not conform to protocol 'Equatable'
    let name: String
    let age: Int
    let void: Void
}
```

You can get around this by implementing `==` yourself, but that's a lot of boilerplate for a simple struct.

```swift
struct Person {
    let name: String
    let age: Int
    let void: Void
}

extension Person: Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.name == rhs.name && lhs.age == rhs.age
        // Can also put
        // && lhs.void == rhs.void
        // and it will work, but it's not necessary
        // just unexpected that it won't autogenerated, but the definition of an equality operator doesn't automatically create equatable conformance
    }
}
```

So, there are a couple of options at this point
 - Create a variant of every generic type that you want to use with `Void` that removes the void field. This is a lot of boilerplate.
 - Create a nominal type that has the same effect as void but conforms to most protocols. An example (or so copilot tells me) would be something like

```swift
struct Void: Equatable, Hashable, Codable, ExpressibleByNilLiteral {
    init(nilLiteral: ()) {}
}
```

 - Craft a type that acts like Void, but isn't void. The type I use is `Never?`, which can only have one value, `nil`.

There's probably a way to do this. Please let me know!

```swift
struct Person: Equatable {
    let name: String
    let age: Int
    let void: Never? // works :)
}
```