But the Element type of your BloomFilter here is not S or WrappedS—it's the type of the objects which you are add-ing and maybeHas-ing, i.e., String. Your S and WrappedS are still relying on the underlying guarantee that the 'same' value will always produce the same hash value. In the case of S this arises because we want that two S values containing the same String will always produce the same hasher state when they are hashed as part of WrappedS, and WrappedS further relies on this guarantee to ensure that given the 'same' S and the 'same' salt i, you'll produce the same index for bools every time.
That you've deliberately broken the implementation for == while also ensuring it doesn't get called isn't really material here. The implementation of BloomFilter here only works because of the semantic guarantees imposed on String (and transitively, S) by the combination of Equatable and Hashable.
To look at this another way, suppose that String exposed a public capacity property that gave the current capacity of the underlying buffer, and we implemented your example exactly as above, except that we implemented S.hash(into:) as:
func hash(into hasher: inout Hasher) {
hasher.combine(string)
hasher.combine(string.capacity)
}
Clearly, this gives us a broken BloomFilter implementation because we could potentially have:
var s = "Hello"
filter.add(s)
s.append("!")
s.removeLast()
filter.maybeHas(s) // false
But how can you explain what's broken here without invoking the notion of 'sameness'? And how would you describe what's broken in the following implementation of WrappedS.hash(into:) without invoking the same?
func hash(into hasher: inout Hasher) {
hasher.combine(s)
hasher.combine(salt)
hasher.combine(Int.random())
}
My point, ultimately, is that you haven't really implemented Hashable without Equatable, because there are still very important properties that you want your Hashable implementation to have in order for BloomFilter to work properly. And you'll find that if you try to describe what those properties are, it's precisely a description of the relationship between Hashable and the correct implementation of Equatable for your types.
ETA: Yet another way to think about this is that the relationship between Equatable and Hashable is about more than just particular implementations of protocol requirements. It's about the fundamental idea that hashability requires you to invoke a notion of equality which agrees with your hash value in some way. Yes, it's possible to write algorithms which depend only on hashability and never directly invoke equality, but such algorithms still depend on the underlying equality notion in order to work properly.