59

I want to create a Dictionary that does not limit the key type (like NSDictionary)

So I tried

var dict = Dictionary<Any, Int>()

and

var dict = Dictionary<AnyObject, Int>()

resulting

error: type 'Any' does not conform to protocol 'Hashable'
var dict = Dictionary<Any, Int>()
           ^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Any, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~

OK, I will use Hashable

var dict = Dictionary<Hashable, Int>()

but

error: type 'Hashable' does not conform to protocol 'Equatable'
var dict = Dictionary<Hashable, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Hashable, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~

So Hashable inherited from Equatable but it does not conform to Equatable??? I don't understand...

Anyway, keep trying

typealias KeyType = protocol<Hashable, Equatable> // KeyType is both Hashable and Equatable
var dict = Dictionary<KeyType, Int>() // now you happy?

with no luck

error: type 'KeyType' does not conform to protocol 'Equatable'
var dict = Dictionary<KeyType, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'KeyType' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:6:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<KeyType, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~

I am so lost now, how can I make compiler happy with my code?


I want to use the dictionary like

var dict = Dictionary<Any, Int>()
dict[1] = 2
dict["key"] = 3
dict[SomeEnum.SomeValue] = 4

I know I can use Dictionary<NSObject, Int>, but it is not really what I want.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • 1
    make a class that conforms to Hashable and Equatable? – Joseph Mark Jun 09 '14 at 12:00
  • 1
    @sjeohp how is that useful? it limit the key type to be that class only. and if you really mean make a new `protocol`, that also won't help. because I can't extents every other class to make them conform my protocol – Bryan Chen Jun 09 '14 at 12:05
  • 2
    well one way or another you have to convince Dictionary that your generic key type can be both hashed and compared for equality, since that's how a dictionary works – Joseph Mark Jun 09 '14 at 12:10
  • otherwise you could implement your own dictionary with your own hash/compare methods – Joseph Mark Jun 09 '14 at 12:12
  • @sjeohp have no idea about how generic may help. and I am fighting with the type checker, even with my own dictionary implementation, I still will have same problem. Because the type checker is going to do the same thing to me again on the new dictionary type. – Bryan Chen Jun 09 '14 at 12:14
  • Is there a reason `AnyObject` does not conform to `Hashable` and `Equatable`? Is there a known subtype of `AnyObject` that would not do well by this? – Craig Otis Jun 09 '14 at 12:15
  • 1
    @CraigOtis what do you mean? `AnyObject` is `@class_protocol protocol AnyObject {}`, which by definition, does not conform to any protocol – Bryan Chen Jun 09 '14 at 12:17
  • Right, I know that it *doesn't*, I was just (philosophically) pondering what the world would be like if it *did*, like so: `@class_protocol protocol BetterAnyObject : NSObjectProtocol, Equatable, Hashable {}` – Craig Otis Jun 09 '14 at 12:20
  • @CraigOtis thats basically `NSObject` – Bryan Chen Jun 09 '14 at 12:23
  • I think that might be the answer to your question then - if a `Dictionary` requires that its keys be both equatable and hashable, and `NSObject` is the most generic type that implements both, that's what you need to use as your key. (Even though I know you mention not wanting to use that, it seems the only option with what's currently available.) – Craig Otis Jun 09 '14 at 12:27
  • @CraigOtis well, it is the fallback solution. which stop me put `struct` and `enum` into the dictionary. – Bryan Chen Jun 09 '14 at 12:29
  • Which makes sense. If the compiler can't guarantee that an `enum` will be able to provide a hash at runtime, and the `Dictionary` requires that of its keys, you can't use `enum` types as keys. As another commenter mentions, your best bet might be a custom subclass that supports enum/struct keys. – Craig Otis Jun 09 '14 at 12:31
  • @CraigOtis I guess I can have wrapper class to hold enum and struct. but the compiler _can_ guarantee that because I can make `enum`(and `struct`) conform to `Hashable` and if the type system works as I expected, it should accept anything conform to `Hashable` (`class`, `struct` and `enum)` – Bryan Chen Jun 09 '14 at 12:33
  • 1
    I took the liberty of cross-posting / linking to this question on a separate post on the Apple Dev forums and this question is answered here: https://devforums.apple.com/message/1045616 . – Chris Conover Sep 18 '14 at 22:54
  • Use NSMutableDictionary – fnc12 Dec 23 '14 at 14:47

8 Answers8

72

Swift 3 update

You can now use AnyHashable which is a type-erased hashable value, created exactly for scenarios like this:

var dict = Dictionary<AnyHashable, Int>()

Community
  • 1
  • 1
Yarneo
  • 2,922
  • 22
  • 30
17

I believe that, as of Swift 1.2, you can use an ObjectIdentifier struct for this. It implements Hashable (and hence Equatable) as well as Comparable. You can use it to wrap any class instance. I'm guessing the implementation uses the wrapped object's underlying address for the hashValue, as well as within the == operator.

jules
  • 514
  • 5
  • 8
  • 1
    This is the correct answer to this question, not sure why it isn't marked/voted as such – rudd Dec 21 '15 at 21:52
11

I took the liberty of cross-posting / linking to this question on a separate post on the Apple Dev forums and this question is answered here.

Edit

This answer from the above link works in 6.1 and greater:

struct AnyKey: Hashable {
    private let underlying: Any
    private let hashValueFunc: () -> Int
    private let equalityFunc: (Any) -> Bool

    init<T: Hashable>(_ key: T) {
        underlying = key
        // Capture the key's hashability and equatability using closures.
        // The Key shares the hash of the underlying value.
        hashValueFunc = { key.hashValue }

        // The Key is equal to a Key of the same underlying type,
        // whose underlying value is "==" to ours.
        equalityFunc = {
            if let other = $0 as? T {
                return key == other
            }
            return false
        }
    }

    var hashValue: Int { return hashValueFunc() }
}

func ==(x: AnyKey, y: AnyKey) -> Bool {
    return x.equalityFunc(y.underlying)
}
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
Chris Conover
  • 8,889
  • 5
  • 52
  • 68
  • This worked for me, but I had to add AnyKey("string") at the call site. Something to be aware of if you're designing a public interface. – funroll Sep 30 '15 at 15:34
  • Those unfamiliar with the above "Type Erasure" technique will enjoy @rob-napier's talk: https://www.youtube.com/watch?v=QCxkaTj7QJs – Jason Pepas Mar 18 '16 at 20:15
7

Dictionary is struct Dictionary<Key : Hashable, Value>... Which means that Value could be anything you want, and Key could be any type you want, but Key must conform to Hashable protocol.

You can't create Dictionary<Any, Int>() or Dictionary<AnyObject, Int>(), because Any and AnyObject can't guarantee that such a Key conforms Hashable

You can't create Dictionary<Hashable, Int>(), because Hashable is not a type it is just protocol which is describing needed type.

So Hashable inherited from Equatable but it does not conform to Equatable??? I don't understand...

But you are wrong in terminology. Original error is

type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol' That means that Xcode assuming 'Hashable' as some type, but there is no such type. And Xcode treat it as some kind empty type, which obviously does not conform any protocol at all (in this case it does not conform to inherited protocol Equatable)

Something similar happens with KeyType.

A type alias declaration introduces a named alias of an existing type into your program.

You see existing type. protocol<Hashable, Equatable> is not a type it is protocol so Xcode again tells you that type 'KeyType' does not conform to protocol 'Equatable'

You can use Dictionary<NSObject, Int> just, because NSObject conforms Hashable protocol.

Swift is strong typing language and you can't do some things like creating Dictionary that can hold anything in Key. Actually dictionary already supports any can hold anything in Key, which conforms Hashable. But since you should specify particular class you can't do this for native Swift classes, because there is no such master class in Swift like in Objective-C, which conforms air could conform (with a help of extensions) to Hashable

Of course you can use some wrapper like chrisco suggested. But I really can't imagine why you need it. It is great that you have strong typing in Swift so you don't need to worry about types casting as you did in Objective-C

Silmaril
  • 4,241
  • 20
  • 22
2

Hashable is just a protocol so you can't specify it directly as a type for the Key value. What you really need is a way of expressing "any type T, such that T implements Hashable. This is handled by type constraints in Swift:

func makeDict<T: Hashable>(arr: T[]) {
  let x = Dictionary<T, Int>()
}

This code compiles.

AFAIK, you can only use type constraints on generic functions and classes.

Bill
  • 44,502
  • 24
  • 122
  • 213
  • Is any reason I can't specify `Hashable` directly as a type for the Key? I can do it for value. and my aim it to create dictionary that key can be `Int` and `String` and anything conform to `Hashable` – Bryan Chen Jun 09 '14 at 12:47
  • This will work for any T that implements Hashable, which includes Int and String. – Bill Jun 09 '14 at 12:50
  • 1
    but it only works for Int _or_ String. I want Int _and_ String – Bryan Chen Jun 09 '14 at 21:48
  • That's a pretty unusual use case. Can you explain? – Bill Jun 10 '14 at 00:09
  • thats what `NSDictioanry` can do, the key type just need to conform `NSCopying` which can be `NSString` or `NSNumber` – Bryan Chen Jun 10 '14 at 00:12
  • Yes, but it's typically used with a key of a single type. What is the underlying reason for needing this data structure? – Bill Jun 10 '14 at 01:21
  • mainly because I want to know how to do it in swift. and it can be useful for library code to accept different custom defined key type – Bryan Chen Jun 10 '14 at 02:42
  • 1
    @Bill Who cares what the underlying reason is. The point is Bryan has found a pretty significant limitation in Swift that Obj-C doesn't have. I'm running into a similar problem myself with generics in Swift. Pretty much any other language could do this with Protocols or Interfaces. Swift's implementation is very poor here. – devios1 Jul 24 '14 at 06:05
  • 1
    @chaiguy It's fairly common to ask a poster for context and a higher-level description of the problem. This strikes me as an unusual thing to do, so I asked for more information. – Bill Jul 26 '14 at 01:14
  • @Bill I apologize for my tone. Of course asking for clarification is fine; it just sounded like you were defending the limitation because you didn't consider his reason valid. Tbh I'm getting very frustrated with Swift's limitations, even though I obviously realize it's still prerelease. – devios1 Jul 26 '14 at 19:18
  • I want this functionality to keep track or results from different objects without having to make the objects conform to Hashable, see below for my solution. – Howard Lovatt Aug 08 '14 at 04:36
2

This doesn't exactly answer the question, but has helped me.

The general answer would be implement Hashable for all your types, however that can be hard for Protocols because Hashable extends Equatable and Equatable uses Self which imposes severe limitations on what a protocol can be used for.

Instead implement Printable and then do:

var dict = [String: Int]
dict[key.description] = 3

The implementation of description has to be something like:

var description : String {
    return "<TypeName>[\(<Field1>), \(<Field2>), ...]"
}

Not a perfect answer, but the best I have so far :(

Howard Lovatt
  • 968
  • 1
  • 8
  • 15
  • Thanks for this, it helped me figure out how to solve my particular problem, which I've posted as an answer below. – RenniePet Feb 21 '17 at 05:20
0

This does not answer the OP's question, but is somewhat related, and may hopefully be of use for some situations. Suppose that what you really want to do is this:

   public var classTypeToClassNumber = [Any.Type : Int]()

But Swift is telling you "Type 'Any.Type' does not conform to protocol Hashable".

Most of the above answers are about using object instances as a dictionary key, not using the type of the object. (Which is fair enough, that's what the OP was asking about.) It was the answer by Howard Lovatt that led me to a usable solution.

public class ClassNumberVsClassType {

   public var classTypeToClassNumber = [String : Int]()

   public init() {

      classTypeToClassNumber[String(describing: ClassWithStringKey.self)] = 367622
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList3.self)] = 367629
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList2.self)] = 367626
      classTypeToClassNumber[String(describing: ClassWithGuidKey.self)] = 367623
      classTypeToClassNumber[String(describing: SimpleStruct.self)] = 367619
      classTypeToClassNumber[String(describing: TestData.self)] = 367627
      classTypeToClassNumber[String(describing: ETestEnum.self)] = 367617
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList0.self)] = 367624
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList1.self)] = 367625
      classTypeToClassNumber[String(describing: SimpleClass.self)] = 367620
      classTypeToClassNumber[String(describing: DerivedClass.self)] = 367621
   }

   public func findClassNumber(_ theType : Any.Type) -> Int {
      var s = String(describing: theType)
      if s.hasSuffix(".Type") {
         s = s.substring(to: s.index(s.endIndex, offsetBy: -5))  // Remove ".Type"
      }
      let classNumber = _classTypeToClassNumber[s]
      return classNumber != nil ? classNumber! : -1
   }
}

EDIT:

If the classes involved are defined in different modules, and may have conflicting class names if you neglect the module name, then substitute "String(reflecting:" for "String(describing:", both when building up the dictionary and when doing the lookup.

RenniePet
  • 11,420
  • 7
  • 80
  • 106
-3

You can use the class name as a Hashable, e.g.:

var dict = [String: Int]
dict[object_getClassName("key")] = 3

See How do I print the type or class of a variable in Swift? for how you might get the class name.

Community
  • 1
  • 1
Howard Lovatt
  • 968
  • 1
  • 8
  • 15
  • Oops re-read question following the comment, it doesn't solve the question. It solves the question only if each object in the dict is a different type. This was my use case and therefore it solved my problem, however it is not the question asked - sorry. – Howard Lovatt Aug 08 '14 at 06:16