Some time ago, before I decided to leave Twitter permanently, I shared this snippet of code for generating Hashable
and Equatable
implementations for a struct
that has a type that cannot by definition be Hashable
or Equatable
, such as a stored function in a variable.
As an example, let’s say we have a simple ViewModel
struct that is passed an action
function:
struct ViewModel: Equatable {
let id: String
let name: String
let action: () -> Void // This property causes an error
}
Normally, we’d have to create our own equality function for this struct
, as the compiler isn’t sure what to do with the action
property. The function we’d write would omit the action
from the Equality calculation. Similarly, if this struct was Hashable
, we would also have to do it for the hash function. But what if we could convince the compiler to do it for us instead?
Using property wrappers, we can make this easy!
@propertyWrapper
public struct IgnoreEquatable<Wrapped>: Equatable {
public var wrappedValue: Wrapped
public static func == (
lhs: IgnoreEquatable<Wrapped>,
rhs: IgnoreEquatable<Wrapped>
) -> Bool {
true
}
public init(wrappedValue: Wrapped) {
self.wrappedValue = wrappedValue
}
}
@propertyWrapper
public struct IgnoreHashable<Wrapped>: Hashable {
@IgnoreEquatable public var wrappedValue: Wrapped
public func hash(into hasher: inout Hasher) {}
public init(wrappedValue: Wrapped) {
self.wrappedValue = wrappedValue
}
}
Now, instead of writing our ViewModel
with a hand-written equality function, we can just write it like so:
struct ViewModel: Equatable {
let id: String
let name: String
@IgnoreEquatable var action: () -> Void // Note the change from `var` to `let`
}
And if we instead wanted it to be Hashable
, we could just use the @IgnoreHashable
wrapper:
// Hashable requires Equatable, but will automatically generate it for you if possible
struct ViewModel: Hashable {
let id: String
let name: String
// Note that @IgnoreHashable wraps its value with `@IgnoreEquatable`. Writing `@IgnoreHashable @IgnoreEquatable` would be incorrect.
@IgnoreHashable var action: () -> Void
}
Updated 04/19/2023
A previous version of this article did not update the declaration of action
to be a var
and left them as let
. This error was pointed out by reader Parthasarathy Gudivada – thank you for the correction!