[Pitch] Automatic `Hashable` Conformance for Tuples (Revival of SE-0283)

[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.

4 Likes

This feature has already been accepted by SE-0283 but implementation difficulties prevented it landing. It would likely be considered again if the language gains the ability to add general conformances to non-nominal types.

This is not actually the case. The compiler doesn't synthesize an Equatable conformance; the standard library has hard-coded == operator implementations for tuples up to arity six.

5 Likes

Now that we have parameter packs, the old plan of hard-coding certain tuple conformances directly in the compiler is obsolete. A more recent pitch that reflects the current plan is here: Pitch: User-defined tuple conformances

The blocker at this point is just that the parameter pack implementation is incomplete. Once some holes in SILGen are addressed, it would be pretty easy to finish the remaining parts needed to bring up tuple conformances to Hashable, Comparable, and whatever else (CustomStringConvertible perhaps?) in the standard library.

13 Likes

This is actually not valid today.

For what it’s worth, Array conforms to Hashable when its element type does, so Array<String> is already Hashable.

Also just to satiate your own curiosity: Swift doesn’t have single-element tuples. The parentheses have no semantic meaning in this case.

6 Likes

Do you know if anyone is working on the parameter pack features needed for this and if so how much is left before we can start using it?

1 Like

@allevato @Slava_Pestov Thank you for the detailed feedback and for pointing me to SE-0283! I appreciate you taking the time to review this.

You're absolutely right on both counts:

  • This is essentially a re-pitch of SE-0283 (Introduce Equatable, Comparable, and Hashable conformance for tuples whose elements conform). I hadn't connected the dots when drafting, my apologies for the oversight. The acceptance in 2020 was exciting, but the revert due to implementation hurdles (runtime/ABI issues) makes sense why it's still blocked.

  • On Equatable: Corrected - tuples rely on stdlib overloads up to arity 6 (The Swift standard library has pre-written == functions for tuples with 1 to 6 elements, but not 7 or more.), not synthesized conformances. I'll update the pitch to reflect that (no changes needed for `==`, but full protocol conformance would enable more, like `Set` membership).

Given the ongoing work on non-nominal conformances (e.g., discussions in Swift 5.9+), do you think this is worth revisiting now? If so, I'd love pointers on:

  • Recent threads/PRs tackling the runtime hooks.

  • Whether focusing *just* on `Hashable` (as a minimal viable step) could unblock dictionary keys without the full E/C/H suite.

Happy to iterate on this or close if it's too duplicative. Thanks again, this community rocks! :rocket:

@Slava_Pestov — Thank you again for the precise and invaluable feedback! I’ve updated the pitch to reflect all your corrections:

  • Removed the invalid extension (Int, String): Hashable example and marked it as “invalid today”
  • Updated the conformance table: (Int, [String])Yes (since Array<String>: Hashable)
  • Removed single-element tuples - great catch! (Int) is just Int, not a tuple
  • Clarified that == is provided via stdlib overloads up to arity 6, not synthesized

All changes are live in the UPDATE banner at the top.


Given that SE-0283 was accepted but reverted due to non-nominal conformance challenges, and that compiler work in this area has progressed since 2021. Do you think this is a realistic time to revive implementation?

Even a minimal first step (e.g. Hashable for tuples up to arity 6) could unblock dictionary keys without waiting for full non-nominal support.

I’d be happy to help draft a formal SE-XXXX or contribute to a prototype if there’s interest.

Thanks again, your guidance is making this pitch much stronger!

It's not even missing features, just a handful of bugs. I think the biggest blocker is that the following doesn't work:

func f<each T>(_: (repeat each T)) {}

func g<each T>(_ t: (repeat each T)) {
  f(t)
}

Once this works, SILGen should be able to emit witness thunks for tuple conformances. The remaining step is sorting out a backward deployment mechanism (it would be a bit unfortunate if Equatable/Hashable/Comparable tuple conformances in the stdlib had a deployment target restriction, because that would prevent us from removing the silly fixed-arity overloads.)

6 Likes

These are ABI anyways, so we wouldn’t be able to fully remove them anytime soon anyway.

1 Like

Actually I misspoke. I remembered that this PR exists which introduces concrete parameter pack overloads for those operators (but not conformances): De-gyb Tuple.swift.gyb and replace it with variadic generics by harlanhaskins · Pull Request #85373 · swiftlang/swift · GitHub and banishes the old ones to @usableFromInline so the entry points still exist but they don’t participate in overload resolution. But I still think it would be better if the conformances could backward deploy too, once the conformances are a thing.

3 Likes

This is false, as it stands today.

2 Likes

@sveinhal Thanks for your correction! I will fix this in the pitch. Good catch!

@allevato @Slava_Pestov - Could you please reopen my original post for a quick edit? I need to fix the Empty tuple() Hashable status per @sveinhal feedback. Thanks!