I'm trying to write a simple unit test to call zlib’s inflate method, which involves C interop. My code looks like this:
@Test func testInflate() throws {
var state = z_stream()
inflateInit_(&state, ZLIB_VERSION, Int32(MemoryLayout<z_stream>.size))
defer { inflateEnd(&state) }
var compressedData = try Data(contentsOf: URL(filePath: "/Users/rmann/Desktop/data.lzw")!) // 'withUnsafeMutableBytes' is deprecated:
compressedData.withUnsafeMutableBytes
{ inCompressedData in
state.next_in = inCompressedData
// state.avail_in = inCompressedData.count
}
}
The first couple things I ran into:
As written, it warns "withUnsafeMutableBytes is deprecated: use withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R instead".
Near as I can tell, these only differ in the type of the parameter passed to the body. Why does Swift choose the deprecated one?
If I uncomment the assignment to state.avail_in, I get a compiler error: "Cannot assign value of type UnsafeMutableRawBufferPointer to type UnsafeMutablePointer<Bytef> (aka UnsafeMutablePointer<UInt8>)" on the line above.
If I make the body take UnsafeMutableRawBufferPointer, then it complains about the assignment to state.next_in: "Cannot assign value of type UnsafeMutableRawBufferPointer to type UnsafeMutablePointer<Bytef>(aka UnsafeMutablePointer<UInt8>)". It’s like referencing .count made it change the inferred type of the body parameter. That seems creepy.
All of your questions have the fundamentally the same answer, which is that your code is written in such a way as it unavoidably runs into a few type issues.
Note that the type of the parameter passed to your closure varies in two ways: <T> vs Raw, and nothing vs Buffer. With those in mind, we can now answer your questions in order:
Why does Swift choose the deprecated one?
Because your code won't compile otherwise. You cannot assign UnsafeMutableRawBufferPointer to a field that wants an UnsafeMutablePointer<UInt8>. You can assign UnsafeMutablePointer<UInt8> to that field. So one choice emits a deprecation warning, the other emits a compile error. Better to make the deprecation choice.
1If I uncomment the assignment to state.avail_in, I get a compiler error: "Cannot assign value of type UnsafeMutableRawBufferPointer to type UnsafeMutablePointer<Bytef> (aka UnsafeMutablePointer<UInt8>)" on the line above.
This changes Swift's choices. Now, if it chose either option, the code doesn't compile, so it chooses the non-deprecated option and gives you the compile errors from that path. The bug, again, is that you are assigning the buffer pointer to the pointer. Use .baseAddress. You are right that referencing .count changed the type inference, but only because it made both parts equally unable to compile.
A quick reminder: your pointers must not outlive the scope they are set in. Please ensure you do all your work with the zlib state before you return from those functions. NIO has an example of a correct use of zlib that uses ByteBuffer, but the approach should be largely amendable to any other bucket of bytes.