Loading misaligned values from raw buffers

should there be an easier way to load misaligned values from raw buffers? Right now I have this for loading arbitrary big endian FixedWidthIntegers from an ArraySlice

func load<T, U>(bigEndian:T.Type, as type:U.Type, from slice:ArraySlice<UInt8>) -> U 
    where T:FixedWidthInteger, U:BinaryInteger
{
    return slice.withUnsafeBufferPointer 
    {
        (buffer:UnsafeBufferPointer<UInt8>) in
        
        var storage:T = .init()
        let value:T   = withUnsafeMutablePointer(to: &storage) 
        {
            $0.deinitialize(count: 1)
            
            guard buffer.count >= MemoryLayout<T>.size, 
              let source:UnsafeRawPointer = buffer.baseAddress.map(UnsafeRawPointer.init(_:))
            else 
            {
                fatalError("attempt to load \(T.self) from buffer of size \(buffer.count)")
            }
            
            let raw:UnsafeMutableRawPointer = .init($0)
            raw.copyMemory(from: source, byteCount: MemoryLayout<T>.size)
            
            return raw.load(as: T.self)
        }
        
        return U(T(bigEndian: value))
    }
}

this is really hard to get right (did you remember to deinitialize storage?) and it’s really easy to accidentally make a memory-unsafe implementation that works for standard library integer types but not the generic family

You can't do this for an arbitrary T. copyMemory and load are still undefined when applied to unaligned pointers, and for nontrivial types, the value witnesses for T fundamentally assume alignedness. If T is trivial, you can use memcpy to do unaligned copies, like C. As for the question of "should this be easier", well, yes. Ideally UnsafeRawPointer and UnsafeMutableRawPointer would have loadUnaligned and storeBytesUnaligned methods that asserted that the given type was trivial and did the unaligned memory accesses.

why is copyMemory undefined for unaligned pointers? it’s a raw pointer method

Ah, you're right, copyMemory is the same as memcpy. This would still only work for trivial types, in which case the deinitialization is a no-op, at least.

In order to provide the destination storage, it'd also be handy if Optional had an uninitialized buffer initializer analogous to what SE-223 proposes for array:

extension Optional {
  init(unsafeUninitializedState: inout Bool, _ body: (UnsafeMutablePointer<Wrapped>) throws -> Void) rethrows
}

or we had a withUnsafeUninitializedStorage(of: T.Type) -> T global function for unconditional initializations.

2 Likes