unsafeRawBufferPointer in Swift 5

I'm updating my code for Swift 5, and I'm having trouble transitioning to the new version of Data.withUnsafeBytes().

A case that seemed to work (the git_ calls are C functions from libgit2, full context here):

let result = data.withUnsafeBytes {
  (bytes: UnsafePointer<Int8>) -> Int32 in
  var entry = git_index_entry()
   
  return path.withCString {
    (path) in
    entry.path = path
    entry.mode = GIT_FILEMODE_BLOB.rawValue
    return git_index_add_frombuffer(index, &entry, bytes, data.count)
  }
}

I changed the type of the bytes parameter to UnsafeRawBufferPointer, and then passed bytes.baseAddress instead of just bytes to git_index_add_frombuffer and the compiler was happy with that.

Then I hit some cases where it was harder to make the compiler happy. I started with, for example, this (full context here):

newData.withUnsafeBytes {
  (bytes) in
  result = git_diff_blob_to_buffer(oldGitBlob, nil,
                                   bytes, newData.count, nil, nil,
                                   git_diff_delta.fileCallback,
                                   nil, nil, nil, &self)
}

If I change bytes to explicitly (bytes: UnsafeRawBufferPointer) like I did before, then the compiler says:

'UnsafeRawBufferPointer' is not convertible to 'UnsafePointer<_>'

Why is it trying to do that conversion? I thought I was doing the same fix here.

1 Like

That sounds like you didn't add the baseAddress part yet.

No, adding the .baseAddress has no effect on the error message.

Hm. :-( I guess it must be a bad diagnostic. Try adding a dummy print() to your closure to turn it into a multi-statement closure; that'll tell the compiler to type-check each statement on its own and possibly produce more useful diagnostics.

1 Like

Alright, I got it worked out. In the second case, I had to pass bytes.bindMemory(to: Int8.self).baseAddress instead of just bytes.baseAddress because in C that parameter is declared as const char * rather than const void *.

It was also giving me grief about that &self reference having mutability issues - this is in an extension to a C struct - but once I got bytes passed correctly that error went away.

1 Like

I think it's common to want to pass a type-erased Data off to as a C string. I wish we had a user friendly API, but we don't have compiler support to guarantee safety yet.

I think this makes the most sense for now:

func takeCString(_: UnsafePointer<CChar>) {/*...*/}

func foo(data: Data) {
  data.withUnsafeBytes {
    (bytes) in
    takeCString(bytes.baseAddress!.assumingMemoryBound(to: CChar.self))
  }
}

If takeCString is implemented in C, which is the common case, this will always be safe. Otherwise, the user is making a promise here that Data's memory is only ever accessed by Swift code as CChar.

2 Likes