New Hashable conformances in Swift 6.4


Greetings, traveler!

Swift 6.4 adds Hashable conformances for several standard library types that could already be compared but could not be stored in a Set or used as dictionary keys.

The changes come from two Swift Evolution proposals:

  • SE-0514 covers Dictionary.Keys, CollectionOfOne, and EmptyCollection.
  • SE-0523 covers UnownedTaskExecutor.

These additions are small, but they remove unnecessary conversions and make several generic and concurrency patterns easier to express.

Hashable Dictionary.Keys

Accessing the keys property of a dictionary returns a Dictionary.Keys view:

let payload: [String: Any] = [
    "id": 42,
    "name": "Swift"
]

let keys = payload.keys

Before Swift 6.4, this view could be iterated and compared, but it did not conform to Hashable. Code that needed a hashable collection had to create a separate Set:

let keys = Set(payload.keys)

Swift 6.4 allows the keys view to be used directly:

var knownSchemas: Set<[String: Any].Keys> = []

knownSchemas.insert(payload.keys)

The dictionary value type does not need to conform to Hashable. Dictionary keys already have a Hashable requirement, so [String: Any].Keys can still receive the new conformance.

Equality uses set semantics

Two Dictionary.Keys values are equal when they contain the same keys. Dictionary values and key iteration order do not affect the result.

let first = [
    "id": 1,
    "name": 10
]

let second = [
    "name": 20,
    "id": 2
]

print(first.keys == second.keys) // true

Both dictionaries contain the keys "id" and "name", so their key views are equal.

Hashing follows the same semantics. Reordering the keys does not change the hash. This is required because dictionary iteration order is not part of a dictionary’s identity.

When order matters, use an ordered collection instead:

let orderedKeys = dictionary.keys.sorted()

An array or sorted key collection makes the ordering requirement explicit.

Where Dictionary.Keys is useful

The new conformance is useful when the set of fields matters more than the values stored under them.

For example, an application can track the schemas of dynamic payloads without allocating a separate Set for each dictionary:

struct PayloadSchemaRegistry<Value> {
    private var schemas: Set<Dictionary<String, Value>.Keys> = []

    mutating func register(_ payload: [String: Value]) {
        schemas.insert(payload.keys)
    }

    func contains(_ payload: [String: Value]) -> Bool {
        schemas.contains(payload.keys)
    }
}

Possible use cases include:

  • Deduplicating payload structures
  • Caching results by available fields
  • Grouping metadata by key set
  • Passing dictionary key views into generic APIs that require Hashable

For long-lived storage, a standalone Set may still communicate the intent better:

let storedKeys = Set(dictionary.keys)

Dictionary.Keys is a view tied to a specific dictionary type, including its value type. Set<Key> is often easier to expose across API boundaries.

Why Dictionary.Values is not Hashable

SE-0514 does not add Hashable to Dictionary.Values.

Dictionary keys are unique, so a keys view has natural set semantics. Values can contain duplicates:

let values = [
    "first": 1,
    "second": 1,
    "third": 2
].values

Ignoring order while preserving duplicate counts would require multiset semantics. Swift does not currently have a standard multiset abstraction, and implementing efficient equality would require extra storage or additional constraints.

The proposal therefore limits the conformance to types with clear equality and hashing rules.

Hashable CollectionOfOne

CollectionOfOne<Element> is a collection containing exactly one element. Swift 6.4 adds conditional Equatable and Hashable conformances:

let first = CollectionOfOne("Swift")
let second = CollectionOfOne("Swift")

let collections: Set<CollectionOfOne<String>> = [
    first,
    second
]

print(collections.count) // 1

The collection is hashable when its element is hashable. Its hash is based on that single element.

This type rarely appears in application code, but the conformance matters in generic algorithms that work with different collection implementations.

Hashable EmptyCollection

EmptyCollection<Element> also becomes hashable:

let empty = EmptyCollection<String>()
let values: Set<EmptyCollection<String>> = [empty]

All instances of the same EmptyCollection<Element> type are equal because none of them contains an element.

Like the CollectionOfOne change, this mainly improves consistency in generic code.

Hashable UnownedTaskExecutor

SE-0523 adds Hashable to UnownedTaskExecutor.

An UnownedTaskExecutor represents a non-owning reference to a Swift concurrency executor. It already supported equality, which allowed code to check whether two references represented the same executor.

Without Hashable, mapping executors to resources required a list and a linear search:

struct PoolEntry {
    let executor: UnownedTaskExecutor
    let connection: DBConnection
}

let connection = entries.first {
    $0.executor == executor
}?.connection

Swift 6.4 allows the executor to be used directly as a dictionary key:

var connections: [
    UnownedTaskExecutor: DBConnection
] = [:]

connections[executor] = connection

let connection = connections[executor]

A dictionary provides an average O(1) lookup instead of scanning an array in O(n) time.

This is useful for connection pools and other infrastructure that associates a resource with a specific executor.

Task executors and actor isolation

A task executor determines where Swift task jobs can run. It should not be confused with actor isolation.

Actor isolation protects mutable state. A task executor controls task execution. Some concurrency infrastructure needs to identify the executor currently running a task and select a matching resource.

The current executor can be inspected through withUnsafeCurrentTask:

func connectionForCurrentExecutor() -> DBConnection? {
    withUnsafeCurrentTask { task in
        guard let executor = task?.unownedTaskExecutor else {
            return nil
        }

        return connections[executor]
    }
}

This is a low-level concurrency API. Most SwiftUI and UIKit applications will not need it directly. It is more relevant to server libraries, custom executors, database pools, and scheduling infrastructure.

Unowned executor lifetime

UnownedTaskExecutor does not retain the executor it references.

Storing it as a dictionary key does not keep the executor alive:

var resources: [
    UnownedTaskExecutor: Resource
] = [:]

The owner of this dictionary must remove entries when the associated executor shuts down. Otherwise, the dictionary can contain keys that refer to executors whose lifetime has ended.

The new Hashable conformance improves lookup performance, but it does not change the ownership rules of the type.

Final thoughts

Dictionary.Keys: Hashable is the most broadly useful change. It allows key views to participate directly in sets, dictionary keys, and generic APIs without first converting them into another collection.

The CollectionOfOne and EmptyCollection conformances complete missing parts of the standard library’s generic model.

UnownedTaskExecutor: Hashable targets a narrower problem, but it gives concurrency infrastructure an efficient way to associate resources with executor identities. The main requirement remains the same: the code storing an unowned executor must also manage its lifetime correctly.