For example, without this second overload, loading an Array will yield "UnsafeRawBufferPointer.load out of bounds". Is there a way to handle both cases without overloads?
let bytes: [UInt8] = [1, 0, 1, 0]
bytes.load() as Int32 // 0x1_00_01
bytes.load() as [Int16] // [1, 1]
public extension ContiguousBytes {
func load<T>(_: T.Type = T.self) -> T {
withUnsafeBytes { $0.load(as: T.self) }
}
func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
withUnsafeBytes { .init($0.bindMemory(to: Element.self)) }
}
}
In terms of having two overloads: you unfortunately can't avoid them. ContiguousBytes.withUnsafeBytes
gives you access to the underlying storage of a type which implements ContiguousBytes
, so the buffer given by, say, [UInt8].withUnsafeBytes
will be the actual buffer the array instance is using to store data, not a pointer to itself in memory (e.g. its length, capacity, storage pointer, etc.).
The reason your first overload can't work is that you'd be calling the equivalent of:
withUnsafeBytes { rawBuffer in
rawBuffer.load(as: [Int16].self)
}
where load(as:)
is attempting to read [1, 0, 1, 0]
as if those contents were the length, capacity, and buffer pointer of a single [Int16]
instance, but that's not the case — it's only the contents of bytes
.
You need the second overload in order to create a new array with the contents of that UnsafeRawBufferPointer
.
As for the actual implementation of your array variant, as of SE-0333 Expand usability of withMemoryRebound coming in Swift 5.7, the correct way to do this would be to use withMemoryRebound(to:_:)
, as the unconditional bindMemory(to:)
is unsafe if the buffer was already bound to a different type. withMemoryRebound(to:_:)
is now always safe, even if the buffer is bound, so long as you meet alignment requirements:
/// A raw buffer may represent memory that has been bound to a type.
/// If that is the case, thenT
must be layout compatible with the
/// type to which the memory has been bound. This requirement does not
/// apply if the raw buffer represents memory that has not been bound
/// to any type.
This would look like
func load<Element>(_: [Element].Type = [Element].self) -> [Element] {
withUnsafeBytes { rawBuffer in
rawBuffer.withMemoryRebound(to: Element.self) { typedBuffer in
Array(typedBuffer)
}
}
}
UnsafeRawBufferPointer.withMemoryRebound(to:_:)
will do the math on binding the raw bytes to the right stride of Element
, though keep in mind that this is only valid to do if the alignment of the raw buffer matches the alignment requirements of Element
— otherwise you'll either crash or trigger undefined behavior.