How do I initialize a C struct from a Data object?

Hi, I’m an expert C/C++ coder, and mostly comfortable in Swift, but I often end up needing to bridge to C APIs, and this always gets me confused and frustrated due to all the all the different unsafe pointer and buffer types and “withUnsafe…” methods.

Today’s saga: All I want to do is initialize a C struct (an array of 32 bytes) from a Swift Data object. (”All I wanted was a Pepsi! Just one Pepsi! And she wouldn’t give it to me!”)

I’ve got a C declaration like this:

typedef struct { uint8_t bytes[32]; } SHSPublicKey;  ///< A 256-bit Ed25519 public key.

It gets mapped to Swift as the rather silly-looking

public struct SHSPublicKey {

    public init() ///< A 256-bit Ed25519 public key.

    public init(bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8))

    ///< A 256-bit Ed25519 public key.
    public var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
}

My handmade Swift wrapper is:

struct PublicKey {
    init?(_ data: Data) {
        guard data.count == MemoryLayout<SHSPublicKey>.size else {return nil}
        _key = SHSPublicKey()    # Ugh, compiler forces redundant init
        withUnsafeMutablePointer(to: &_key) { ptr in
            data.copyBytes(to: ptr, count: MemoryLayout<SHSPublicKey>.size) // ERROR
        }
    }

    fileprivate var _key: SHSPublicKey  // HACK: Should be `let`
}

This fails with: error: cannot convert value of type 'UnsafeMutablePointer<SHSPublicKey>' to expected argument type 'UnsafeMutablePointer<UInt8>' because instance method 'copyBytes(to:count:)' was not imported from C header … which makes no sense to me because copyBytes is a method of the standard Data class, not something from my C header. … Help?

(Also note that Swift forced me to zero-initialize _key even though it’s redundant, saying I can’t pass it as inout if it’s uninitialized. And _key really should be declared as let since it’s supposed to be immutable, but if I do that, Swift won’t let me pass it as an inout parameter to initialize it :exploding_head:)

PS: If there is any good book / article about C interop and pointers, I’d love to know. The Swift book says nothing, the Apple API docs just describe individual methods without showing how to use them, and the blog posts I’ve found are pretty shallow and don’t discuss pointers.

1 Like

I’ve tried fixing the error with a type-cast, ptr as! UnsafeMutablePointer<UInt8>, but that produces the warning warning: cast from 'UnsafeMutablePointer<SHSPublicKey>' to unrelated type 'UnsafeMutablePointer<UInt8>' always fails.

At this point I’m going to rage-quit Swift go make some calming herbal tea, go to my happy place, and take slow deep breaths.

One way to accomplish this (since the SHSPublicKey type is just a wrapper around a tuple...) is something like this:

struct PublicKey {
  init?(_ data: Data) {
    guard data.count == MemoryLayout<SHSPublicKey>.size else { return nil }
    _key = data.withUnsafeBytes { buffer in
      buffer.baseAddress!.assumingMemoryBound(to: SHSPublicKey.self).pointee
    }
  }
  
  fileprivate let _key: SHSPublicKey
}
2 Likes

Thanks! I made a wrapper function so I can reuse it easily:

fileprivate func fromData<T>(_ data: Data) -> T {
    return data.withUnsafeBytes { buffer in
        buffer.baseAddress!.assumingMemoryBound(to: T.self).pointee
    }
}
2 Likes

One word of caution: be careful with that. T in those cases must always be bitwise representations - e.g. holding String or [Int] etc won't do what you might first expect. The T must always be a composition of "plain-ol'-data" types e.g. Int, UInt16, CChar, Float, tuples containing those types of things, and structs containing those types of things etc.

load(as:) is probably a better (or at least more readable) choice than the buffer.baseAddress!.assumingMemoryBound(to:).pointee chain; e.g.

extension SHSPublicKey {
  init?(_ data: Data) {
    guard data.count == MemoryLayout<SHSPublicKey>.size else { return nil }
    self = data.withUnsafeBytes { buffer in
        buffer.load(as: SHSPublicKey.self)
    }
  }
}
6 Likes

+1 for load, but I'd actually recommend its cousin, loadUnaligned.

Loading structures directly from packed raw data was one of the primary motivations for adding loadUnaligned.

4 Likes