I have a struct that needs to be Decodable and Hashable. This struct has a property that is of a Protocol type. Depending on the type a concrete value of the protocol is filled in the struct. But how do I make this struct hashable without making the protocol Hashable (which makes it a Generic protocol which can't be directly used)?
enum Role: String, Decodable, Hashable {
case developer
case manager
....
}
protocol Employee {
var name: String { get }
var jobTitle: String { get }
var role: Role { get }
}
struct Manager: Employee, Hashable {
let name: String
let jobTitle: String
let role: Role = .manager
....
}
struct Developer: Employee, Hashable {
let name: String
let jobTitle: String
let role: Role = .developer
let manager: Employee // Here is the problem
static func == (lhs: Developer, rhs: Developer) -> Bool {
lhs.name == rhs.name &&
lhs.jobTitle == rhs.jobTitle &&
lhs.role == rhs.role &&
lhs.manager == rhs.manager // Type 'any Employee' cannot conform to 'Equatable'
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(jobTitle)
hasher.combine(role)
hasher.combine(manager) // Instance method 'combine' requires that 'H' conform to 'Hashable'
}
}
There are multiple issues with this:
- Just to make one property Hashable/Equatable we need to write the
==
andhash
function with all the properties. - Even though we do that, there is still a problem where the protocol is not Hashable/Equatable.
Is there some other or right way to do this?
IMO, you've done this inside-out. You have an enum that has a 1:1 correspondence to structs. That suggest the enum should contain the data rather than using a protocol:
This is slightly tedious because every property needs a
switch
over every case. If there are limited number of properties, that's fine, but it can be annoying if the list of properties is large and grows.Since Employees are all quite similar, another approach is to just put what varies into their role. (I'm assuming this is all a generic example, since in most organizations, managers also have managers. This may be a time to rethink your types and decide if Managers and Developers are actually different things. If
developer.manager
can be a Developer, why two types?)But you can also keep your current design with a protocol. In your example, you don't need
any Employee
to be Equatable. You need to be able to compare an Employee to an arbitrary other Employee. That's not the same thing.Extend your Employee this way, to allow it to be compared to arbitrary other Employees (which is not the same thing as Equatable, which only means a type can be compared to Self).
With that, your
==
implementation looks like:The Hashable situation is even easier, because Hashable doesn't have an associated type of its own. So you can just include it as a requirement: