[Pitch] Automatic `Hashable` Conformance for Tuples (Revival of SE-0283)
UPDATE (Nov 2025): Incorporated feedback from @Slava_Pestov and @allevato – removed invalid syntax, clarified `Array` hashing, removed non-existent single-element tuples, and noted that `==` is provided via stdlib overloads up to arity 6.
---
Introduction
Tuples are a lightweight, anonymous way to group values. They are commonly used as compound keys (e.g., coordinates, cache keys, dictionary lookups). **Python** allows immutable tuples to be used as dictionary keys out-of-the-box, but **Swift** does not – you cannot use a tuple as a `Dictionary` key unless you manually extend *that exact tuple type* to conform to `Hashable`.
This pitch **revives** the accepted-but-reverted **SE-0283** and proposes **automatic `Hashable` conformance for any tuple whose elements are all `Hashable`**, eliminating boilerplate while preserving Swift’s safety.
---
Motivation
Current Pain Point
```swift
// Does NOT compile
let dict: [(Int, String): Bool] = [(1, "a"): true]
Work-around (invalid today – shown only for illustration):
swift
// NOT legal syntax
extension (Int, String): Hashable { … }
The only real option is to wrap the tuple in a struct or avoid dictionary keys altogether.
Why It’s Inconsistent
-
Int, String, and even (Int, Int, String) (if manually extended) are Hashable.
-
But (Int, String) is not, despite both components being Hashable.
Python Comparison
python
cache = {(1, "a"): True} # Works immediately
Swift should be just as ergonomic for this common pattern.
Proposed Solution
A tuple automatically conforms to Hashable if and only if every element conforms to Hashable.
swift
// After this proposal
let dict: [(Int, String): Bool] = [(1, "a"): true]
let set : Set<(Double, Int, Bool)> = [(1.0, 2, false)]
The compiler synthesizes:
swift
extension (T0, T1, ..., Tn): Hashable
where T0: Hashable, T1: Hashable, ..., Tn: Hashable
{
public func hash(into hasher: inout Hasher) {
hasher.combine(0) // type discriminator
hasher.combine(self.0)
hasher.combine(self.1)
// …
hasher.combine(self.n)
}
}
Equality (==) is already provided via standard-library overloads for tuples up to arity 6 (no synthesis needed). Full Equatable conformance would be a separate step (SE-0283 also covered that).
Detailed Design
Conformance Rules
| Tuple Elements | Hashable under proposal? | Reason |
|---|---|---|
| All Hashable | Yes | e.g. (Int, String) |
| Any non-Hashable | No | e.g. (Int, NSObject) |
| Array where T: Hashable | Yes | Array is Hashable |
| Nested tuples | Yes (recursively) | ((Int, Int), String) |
Note: (Int) is not a tuple – parentheses are ignored; it is simply Int.
Hashing Strategy
-
A type discriminator (hasher.combine(0)) prevents collisions between structurally different tuples.
-
Elements are combined in order.
-
Mirrors the strategy used for synthesized struct hashing.
Edge Cases
| Case | Behavior |
|---|---|
| Empty tuple () | Already Hashable |
| Large tuples (10+ elems) | Fully supported (documentation should note performance considerations) |
Source Compatibility
-
Pure addition – no existing code breaks.
-
Manual Hashable extensions (if any) would conflict only if they differ from the synthesized version (rare).
ABI Stability
-
No ABI impact – conformance is source-level.
-
No runtime changes required.
Relation to SE-0283
-
SE-0283 (2020) proposed Equatable / Comparable / Hashable for tuples.
-
It was accepted but reverted in 2021 due to implementation difficulties with non-nominal conformances and runtime hooks.
-
Recent compiler work on general conformances for non-nominal types may now make this feasible.
Alternatives Considered
| Alternative | Drawbacks |
|---|---|
| Keep manual extensions | Boilerplate, inconsistent |
| New HashableTuple type | Unnecessary indirection |
| Allow any tuple in dict | Unsafe (non-hashable elements) |
Acknowledgments
Inspired by Python’s tuple hashing, community requests, and the original SE-0283 proposal.
Let’s bring Swift’s tuples up to Python’s ergonomics – safely and automatically.