What are some practical use cases of AnyHashable?

Is it just for ObjC inter-op? For pure Swift code, it's still needed?

1 Like

For example, you can create a dictionary with heterogeneous key types

var dict = [AnyHashable: String]()
dict[1] = "integer"
dict["2"] = "string"
print(dict) // [AnyHashable(1): "integer", AnyHashable("2"): "string"]
2 Likes

It's not precisely as simple as what you said, but in my experience, it's close. See my comment here for my most common use case.

/// A type whose `Hashable` conformance could be auto-synthesized,
/// but either the API provider forgot, or more likely,
/// the API is written in Objective-C, and hasn't been modernized.
public protocol HashableSynthesizable: Hashable { }

public extension HashableSynthesizable {
  static func == (hashable0: Self, hashable1: Self) -> Bool {
    zip(hashable0.hashables, hashable1.hashables).allSatisfy(==)
  }

  func hash(into hasher: inout Hasher) {
    hashables.forEach { hasher.combine($0) }
  }
}

private extension HashableSynthesizable {
  var hashables: [AnyHashable] {
    Mirror(reflecting: self).children
      .compactMap { $0.value as? AnyHashable }
  }
}
1 Like

Are there any API's that return AnyHashable? Maybe something in ObjC like NSxxx use AnyHashable and return AnyHashable?

Untyped NSDictionary * is bridged to Swift as [AnyHashable: Any]
Untyped NSSet * is bridged to Swift as Set<AnyHashable>

2 Likes
// same either way:
let set = NSSet(objects: 1, 2, 3, 4, 5) as Set
//let set = NSSet(array: [1, 2, 3, 4, 5]) as Set
print(type(of: set))    // Set<NSObject>

let first = set.first!  // should this return AnyHashable?
print(type(of: first), first)   // __NSCFNumber 3

Am I using this wrong? Getting Set<NSObject> and element is __NSCFNumber

How to get Set<AnyHashable> out of NSSet?

AnyHashable is useful when you want an enum case to carry an arbitrary Hashable. For example, SwiftUI's enum CoordinateSpace uses AnyHashable:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public enum CoordinateSpace {

    case global

    case local

    case named(AnyHashable)
}

If an individual case of an enum could be generic, then the named case could be written without AnyHashable:

    case named<H: Hashable>(H)

But that's not allowed. They could have made CoordinateSpace itself generic:

public enum CoordinateSpace<H: Hashable> {
    case global
    case local
    case named(H)
}

But this has other problems. For one, you have to pick some arbitrary Hashable type when you want the global or local space. That is, CoordinateSpace.local is invalid; you have to say something like CoordinateSpace<Int>.local. It also embeds the choice of Hashable into the modified View's type, which means it's part of the modified View's identity (which may be undesirable) and the space name's type can't be changed based on a condition unless you cast to AnyHashable anyway (or use some other way of unifying the space types).

Also note that AnyHashable is quite magical compared to other Any* type erasers. You get implicit conversion to it:

let space: CoordinateSpace = .named(123) // no `as AnyHashable` needed

and you can cast back from AnyHashable to the concrete type:

let h: AnyHashable = 123
print(h as? Double) // prints nil
print(h as? Int)    // prints Optional(123)

Compare to, for example, AnySequence. There is no implicit conversion to it:

  1> let seq: AnySequence<Int> = [123]
error: repl.swift:1:29: error: cannot convert value of type '[Int]' to specified type 'AnySequence<Int>'
let seq: AnySequence<Int> = [123]
                            ^~~~~

You must manually construct it:

  1> let seq: AnySequence<Int> = .init([123])
seq: AnySequence<Int> = { ... }

And you cannot cast back to the concrete type:

  2> seq as? [Int]
$R0: [Int]? = nil

Even though AnyHashable is type erased, it's still type specific you must know its type in order to cast it back to its original type, right?

How does AnyHashable get this power? NSNumber also can be casted this way to any numeric types and Bool. Compiler special bestow magic to these types? Are there more of these magical types?

Without knowing AnyHashable's special cast ability, I was doing this:

anAnyHashable.base as? Int

now I know it's not necessary to use .base, just cast directly. Even the the doc on .base is show this.. The doc should make clear cast directly on AnyHashable is allowed.

dump(anAnyHashable)

â–ż AnyHashable(6)

- value: 6

AnyHashable has this value field, and the type is the actual type. So AnyHashable is very special, like a generic.

The standard library type AnyHashable is written partially in C++

The compiler and the Swift runtime have special support for AnyHashable.

There are other types that have “magical” treatment in the compiler and/or runtime. Two that I can think of off-hand are Error and Optional.

The Error existential type conforms to the Error protocol (this conformance was added so that Result<X, Error> is valid), and the Error existential has a special single-pointer representation (for bridging with Objective-C's NSError).

Optional has too many special treatments to list.

2 Likes

If you define an API in Objective C that uses NSSet *, then it will be bridged to Swift as Set<AnyHashable>.

Objective C declaration:

#pragma once

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

void processSet(NSSet * set);
void processDictionary(NSDictionary * dictionary);

NS_ASSUME_NONNULL_END

Bridged Swift equivalent:

processSet(_ set: Set<AnyHashable>)
processDictionary(_ dictionary: [AnyHashable: Any])
1 Like