Use `Hashable.hash(into:)` or `Hasher.combine(_:)`?

In a StackOverflow comment, there is some confusion about how to hash the components of the value being hashed.

The Hashable documentation says the function should look like

func hash(into hasher: inout Hasher) {
   hasher.combine(id)
   hasher.combine(name)
   hasher.combine(location)
}

However, the WWDC 2018 What’s New in Swift session suggests the function should look like

func hash(into hasher: inout Hasher) {
   id.hash(into: &hasher)
   name.hash(into: &hasher)
   location.hash(into: &hasher)
}

Two questions:

  1. Is there a functional difference between the two?
  2. Which one is recommended or more idiomatic?
1 Like

CC:

1 Like

Hasher's generic combine method is defined in Hasher.swift to simply call hash(into:) on its argument:

  @inlinable
  @inline(__always)
  public mutating func combine<H: Hashable>(_ value: H) {
    value.hash(into: &self)
  }

So hasher.combine(foo) is in fact just an alternative spelling for foo.hash(into: &hasher). They are completely equivalent. Choosing one or the other is a matter of aesthetic preference.

I personally prefer to use the generic combine, because it reads clearer to me. (I find that the inout argument makes hash(into:) rather noisy -- it's a sort of speed bump that makes my brain pause a bit whenever I see it.) This is why I left combine<H>(_:) in the proposal after we removed most of the other combine overloads. However, I can also see reasons for using hash(into:) directly, like Slava did in the WWDC session; after all, hasher.combine(_:) is one more thing you need to learn.

That said, while I do like combine(_:) better, I don't really like to use either form. I overwhelmingly prefer to just let the compiler implement hash(into:) for me automatically. I'd love to do that even more often!

8 Likes