Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dictionary of Optional Keys

Is there any workaround to create a dictionary with Optional keys? I know a proper solution for this would require Conditional Conformances to be implemented, but do I have any options before then?

let dict: [Int?: Int] = [nil: 1]
// ERROR: type 'Int?' does not conform to protocol 'Hashable'
like image 767
Alexander Avatar asked Oct 28 '25 16:10

Alexander


1 Answers

Update: Swift 4.2

Conditional conformances have been implemented in Swift 4.2, woohoo! 🎉

This allowed for Optional to be made conditionally Hashable, when the wrapped element is Hashable. So you can use any Optional<T: Hashable> as a dictionary key, directly!

let d: [Int?: String] = [
    nil: "nil",
    1: "a",
    2: "b",
    3: "c",
]

for (key, value) in d {
    let stringKey = key.map(String.init(describing:)) ?? "nil"
    print("\(stringKey): \(value)")
}

Before Swift 4.2

Here's one workaround I found: Create a new HashableOptional enum:

enum HashableOptional<Wrapped: Hashable> {
    case none
    case some(Wrapped)

    public init(_ some: Wrapped) {
        self = .some(some)
    }

    public init(_ optional: Wrapped?) {
        self = optional.map{ .some($0) } ?? .none
    }

    public var value: Wrapped? {
        switch self {
            case .none: return nil
            case .some(let wrapped): return wrapped
        }
    }
}

extension HashableOptional: Equatable {
    static func ==(lhs: HashableOptional, rhs: HashableOptional) -> Bool {
        switch (lhs, rhs) {
            case (.none, .none): return true
            case (.some(let a), .some(let b)): return a == b
            default: return false
        }
    }   
}

extension HashableOptional: Hashable {
    var hashValue: Int {
        switch self {
            case .none: return 0
            case .some(let wrapped): return wrapped.hashValue
        } 
    }
}

extension HashableOptional: ExpressibleByNilLiteral {
    public init(nilLiteral: ()) {
        self = .none
    }
}

And then you can use it like so:

let dict: [HashableOptional<Int>: Int] = [nil: 1]
like image 139
Alexander Avatar answered Oct 31 '25 08:10

Alexander