Search code examples
swiftgenericshashable

Why does struct need to conform to Hashable as well as Generic array when converting to a dictionary


The goal was to convert items in a generic list into a dictionary for the variable uniqueKeys, but I saw the error:

Cannot subscript a value of incorrect or ambiguous type

I knew something needed to conform to the Hashable protocol and eventually landed on the solution, but I don't completely understand why this solution works.

  1. I understand why T needs to be Hashable since its' going into a dictionary key, but why also CustomSet?
  2. If CustomSet uses Hashable why don't I need to write anything in its extension?

initial code

struct CustomSet<T : Comparable > {
    private var list: [T]
    init(_ list: [T]){
        let uniqueKeys = list.reduce(into: [:]){ dict, item in
            dict[item, default: 0] += 1
        }.keys
        self.list = Array(uniqueKeys)
    }
}

extension CustomSet : Equatable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }
}  

I finally resolved it with:

struct CustomSet<T : Comparable > : Hashable where T : Hashable {
    private var list: [T]
    init(_ list: [T]){
        let uniqueKeys = list.reduce(into: [:]){ dict, item in
            dict[item, default: 0] += 1
        }.keys
        self.list = Array(uniqueKeys)
    }
}

extension CustomSet : Equatable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }
}  

I also noticed that factoring into an extension: extension CustomSet: Hashable where T : Hashable doesn't seem to be the same as struct CustomSet<T : Comparable > : Hashable where T : Hashable in terms of adding the Hashable protocol because I still see that error

What I tried

If I add this to the generic type T I still saw the same error.

struct CustomSet<T : Comparable, Hashable >

If I add Hashable to CustomSet I see the error

Cannot convert value of type '[T]' to expected argument type 'UnsafeRawBufferPointer'

and

Inheritance from non-protocol type 'Hashable'

extension CustomSet : Equatable, Hashable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }

    func hash(into hasher: inout Hasher) {
       var hashValue: Int {
           var hasher = Hasher()
           self.hash(into: &hasher)
           return hasher.finalize()
       }
    }
 }

Solution

  • There's no need to make CustomSet conform to Hashable. Simply adding the Hashable restriction to the generic type parameter solves the error

    Cannot subscript a value of incorrect or ambiguous type

    ,which is expected, since a Dictionary's keys need to conform to Hashable.

    Btw the syntax for declaring conformance to several protocols is &, not ,, so there's no need for the where clause.

    struct CustomSet<T : Comparable & Hashable> {
        private var list: [T]
        init(_ list: [T]){
            let uniqueKeys = list.reduce(into: [:]){ dict, item in
                dict[item, default: 0] += 1
                }.keys
            self.list = Array(uniqueKeys)
        }
    }
    
    extension CustomSet : Equatable {
        static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
            return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
        }
    }
    

    Moreover, you can simply do self.list = Array(Set(list)) in the initializer to store only the unique elements from the input in your list, there's no need for messing around with a Dictionary.