Why isn't my (Int, Int) Hashable?

I have several functions that, internally, use tuples of (Int, Int) and (Int, Int, Int) as Dictionary keys and Array items. (Actually it's by type alias, but substituting it in doesn't seem to fix this.)

All of a sudden I'm getting:

Type '(Int, Int)' does not conform to protocol 'Hashable'
Type '(Int, Int, Int)' does not conform to protocol 'Hashable'

It looks like tuples are supposed to be Equatable, Comparable, and Hashable, so what am I getting wrong?

This was specified in SE-0283 and although an implementation was merged at the time, it was quickly reverted, and the feature has never been implemented.

2 Likes

Oh I see, what else is left to implement this?

I remember this working for me earlier, though. It also looks like I may have been specifying

extension (Int, Int): Comparable, Hashable {
	public static func < (lhs: (Int, Int), rhs: (Int, Int)) -> Bool {
		return lhs.0 < rhs.0 || (lhs.0 == rhs.0 && lhs.1 < rhs.1)
	}
}

But now this is crashing the compiler, as of Xcode 26. I'm not actually sure this ever worked but something was working.

Swift standard library already overloads the binary < (and other comparison operators) for tuples of 0, 2, 3, 4, 5, or 6 Comparable elements. It’s just the Comparable conformance itself that’s missing.

1 Like

Simply declaring the conformance without the implementation still crashes the compiler, saying "Command SwiftCompile failed with a nonzero exit code"

Is there a particular reason the conformance isn't declared? That seems like a trivial thing to add after five years of being approved.

I'm glad of it. Having tuples be able to conform to any protocols, but not all of them, would be inconsistent. I would not want to remember which protocols were supported.

Although I would prefer for tuple extensions to get the funding they need to no longer be experimental, parameter packs help in the interim.

let dictionary: [Pack: _] = [
  .init(1, 2.0): "3",
  .init(4, 5.0): "6"
]
public struct Pack<each Element> {
  public init(_ wrappedValue: repeat each Element) {
    self.wrappedValue = (repeat each wrappedValue)
  }

  public var wrappedValue: (repeat each Element)
}

extension Pack: Equatable where repeat each Element: Equatable {
  public static func == (pack0: Self, pack1: Self) -> Bool {
    for elements in repeat (each pack0.wrappedValue, each pack1.wrappedValue) {
      guard elements.0 == elements.1 else { return false }
    }
    return true
  }
}

extension Pack: Hashable where repeat each Element: Hashable {
  public func hash(into hasher: inout Hasher) {
    repeat hasher.combine(each wrappedValue)
  }
}
1 Like

I agree, so why hasn't this been implemented after five years? Tuples are kind of... useless, if you can't compare them or use them in builtin types like Dictionary.

You ought to be able to declare entire protocols as associative, distributive, etc; and then you know if a data structure consists entirely of types that are of such a protocol, the composite struct also conforms to the protocol.

And then I'm not sure why tuples for 0 and 2...6 are defined by hand, a tuple (A, B, C, ...) should just be syntactic sugar for

struct `(a: A, b: B, c: C, ...)` {
let a: A;
let b: B;
let c: C;
...
}

I'm not sure why tuples and structs need to be different concepts at all.

The difference is that a tuple is a non-nominal (ironically structural)–their identity is determined by shape, while a struct is a nominal type–identified by name.

Currently non-nominal types can’t conform to protocols. But it seems it might be allowed in the future.

As an interesting fact, function type is also non-nominal, but you can annotate it with @Sendable to require Sendable conformance. Sendable is a special marker protocol though. I wonder whether having a similar @Hashable tuple type attribute would be a viable idea.

2 Likes

Tuple conformances can be built on top of parameter packs. The idea was pitched here Pitch: User-defined tuple conformances but we never finished the implementation. It’s most of the way there though.

3 Likes

Will there be some focus on the topics in the future or is it on hold?

I would not say that tuples are useless, but I think using tuples as keys in a dictionary (or dictionary-like structures with appropriate subscripts) an important use case, I am using my own implementations for that which does make me think that there is something missing.

My point is these aren't mutually exclusive concepts, they're just different way to identify how a data structure is composed. I can identify a struct by a name, like String, or I can identify a struct based on its contents, like (min: Float, max: Float?), or a mix of the two, e.g. ClosedRange<Int>. Tuples should be the structs that are uniquely identified by their definition; whereas structs have not only a definition, but can be distinguished by their name and relationships, conformances, etc.

I don't think this would be a good idea, I should be able to look at a tuple, and the definition of Hashable, and know it either conforms to Hashable or it definitely does not with no other context. If a tuple is "maybe Hashable/maybe not", that would violate the idea a tuple is uniquely distinguished by its contents (because its behavior would be distinguishable, therefore the tuple is).

Protocols like Equatable, Comparable, and Hashable should be able to be marked as "distributive" so you can tell if a struct is conformant just by looking at it and the types it contains.