Cross posting from https://forums.swift.org/t/how-to-check-two-array-instances-for-identity-equality-in-constant-time/78792/48:
Please let's not expose identities as escapable objects; I fully concur with @Slava_Pestov's note.
I agree there is a legitimate need for quick isIdentical(to:)
checks, and I think it would be a good idea to add such methods as public members on any concrete type where it makes sense: types that implement the copy on write optimization, on referential types like UnsafeBufferPointer
, etc.
SE-0447 has already established the name isIdentical(to:)
. We added these to support simple tests for referential equality without having to conform Span
/RawSpan
to Equatable
. (It would not have been possible to do that, as Equatable currently requires escapability; but even if we fixed that (as we're planning to do), the conformance would be highly ambiguous: some people would read span1 == span2
to compare identities, others would expect it to compare contents. We do intend Span
to conform to a collection-like protocol, and Equatable
currently carries an expectation for elementwise equality for those. It seemed better to avoid this ambiguity, and instead provide clearly named methods for the two different interpretations.)
I don't really see why we would need to define a standard protocol for this, though. (I'd particularly not like to have to think about types like Bool
getting forced to conform to it.) If the protocol has widespread appeal, then it ought to be possible to demonstrate this by shipping it (and building on it) in a package first -- we don't need to immediately jump to defining it in the Standard Library. The package can make use of concrete isIdentical(to:)
implementations in the stdlib to define its own conformances, as it sees fit.
The one mainstream benefit of a separate standard protocol I do see would be that it would allow us to use the ===
operator as an alias for (or in place of) isIdentical(to:)
, including over wrappers like Optional
. Span
/RawSpan
avoided adding any new overloads of ===
to prevent triggering a tsunami of similar overloads for other types, which would be unlikely to scale well.
An important alternative to a new protocol is to add a isKnownIdentical(to:)
requirement to Equatable
, with a default implementation that returns false
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
@available(SwiftStdlib 6.x, *)
func isKnownIdentical(to other: Self) -> Bool
}
extension Equatable {
@inlinable /* @backDeployed etc */
public func isKnownIdentical(to other: Self) -> Bool { false }
}
This avoids the (mostly mental) overhead of a whole new protocol, but it does put such an operation into the member namespace of every equatable type, which is quite noisy -- so this too would be best proposed once there is widespread agreement that we need to routinely invoke such operations from generic contexts. (Note that default implementations for protocol requirements tend to get compiled into Swift binaries as part of specialization/inlining, and so it will lead to components of an application disagreeing on the return value of isKnownIdentical
. This is not necessarily a problem, but it is a complication.)