How to check if two values of generic type are bitwise equal?

i’ve got a type called InlineBuffer defined as:

@frozen public
struct InlineBuffer<Storage>:Sendable where Storage:Sendable
{
    public
    var storage:Storage

    @inlinable public
    init(storage:Storage)
    {
        self.storage = storage
    }
}

the idea is to use it to wrap some tuple storage, such as:

@frozen public
struct MD5:Sendable
{
    @usableFromInline internal
    typealias Storage = (UInt32, UInt32, UInt32, UInt32)

    @usableFromInline internal
    var buffer:InlineBuffer<Storage>

    @inlinable internal
    init(buffer:InlineBuffer<Storage>)
    {
        self.buffer = buffer
    }
}

but now i am running into trouble with the Equatable conformance, since Storage is a tuple, and cannot itself be constrained to conform to Equatable. the best i could come up with is a really hacky UnsafeRawBufferPointer.elementsEqual(_:)-based comparison that looks like:

extension InlineBuffer:Equatable
{
    @inlinable public static
    func == (lhs:Self, rhs:Self) -> Bool
    {
        withUnsafeBytes(of: lhs.storage)
        {
            (lhs:UnsafeRawBufferPointer) in
            withUnsafeBytes(of: rhs.storage)
            {
                (rhs:UnsafeRawBufferPointer) in
                lhs.elementsEqual(rhs)
            }
        }
    }
}

but this compares the bytes one at a time, and i suspect it might be faster to compare the tuple directly, where is it statically known to have the same length and can go four bytes at a time.

Perhaps you could wrap the tuple in a struct without performance penalty? Then conform this struct to Equatable, and constraint the Storage parameter to be Equatable as well

struct Storage: Equatable {
  var value: (UInt32, UInt32, UInt32, UInt32)

  static func == (lhs: Tup, rhs: Tup) -> Bool {
    lhs.value == rhs.value
  }
}

this was actually how the MD5 type was implemented previously; the motivation for InlineBuffer<Storage> was to be able to share generic implementations for many Storage tuples at once. specifically, i also needed SHA1, which has five UInt32 words:

@frozen public
struct SHA1:Equatable, Hashable, Sendable
{
    @usableFromInline internal
    typealias Storage = (UInt32, UInt32, UInt32, UInt32, UInt32)

    @usableFromInline internal
    var buffer:InlineBuffer<Storage>

    @inlinable internal
    init(buffer:InlineBuffer<Storage>)
    {
        self.buffer = buffer
    }
}

memcmp then?

withUnsafePointer(to: lhs.storage) { lhs in
  withUnsafePointer(to: rhs.storage) { rhs in
    memcmp(lhs, rhs, MemoryLayout<Storage>.size) == 0
  }
}

But with memcmp you should be careful about layout of the Storage types (there shouldn't be holes).
But IMO a few extra lines of code to wrap tuples with structs will give you a much more rigid solution.
Another solution might be to wait for swift 5.9, I think variadic types are the way to go.