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.
dmt
(Dima Galimzianov)
2
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
}
}
dmt
(Dima Galimzianov)
4
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.