withUnsafeMutableBytes deprecated in Swift 5?

Hi,


My code contains the following function:


  func md5(string: String) -> String {
  let messageData = string.data(using:.utf8)!
  var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
            
  _ = digestData.withUnsafeMutableBytes {digestBytes in
       messageData.withUnsafeBytes {messageBytes in
       CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
       }
  }
            
       let md5Hex = digestData.map { String(format: "%02hhx", $0) }.joined()
       return md5Hex
  }


This worked fine and compiled with no errors or warnings in prior versions of Swift. After upgrading to Swift 5, I get a deprecation warning on both "withUnsafeMutableBytes" and "withUnsafeBytes".


I've stared at the warning, read the documentation, and even tried re-keying the code allowing Xcode to fill in what it thinks is the correct syntax, but I cannot resolve the problem. I do not understand what the complaint is, nor what I specifically need to type here to avoid the warnings.


Can someone please explain this, or at least, tell me what I need to write to get it correct?


Thanks.

Replies

The root of the problem seems to be that I need a value of type "unsigned char *" to pass as the thrird parameter to the CC_MD5 function, but what I end up with is of type UnsafeMutableRawPointer? instead.


I tried using as! to cast it, but the compiler says the cast will always fail.

After much trial and error, I arrived at this expression:


            _ = digestData.withUnsafeMutableBytes { (digestBytes) -> Bool in
                messageData.withUnsafeBytes({ (messageBytes) -> Bool in
                    _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
                    return true
                })
            }

It compiles without warning and seems to work, but I'm not confident that I understand why, or if it is correct.


In particular I'm not sure why I had to return a value even though I need no return value. Without that, the compiler seems to think I want the old deprecated function.

In particular I'm not sure why I had to return a value even though I need no return value.


As far as I tried, this code compiles without errors or warnings:

        _ = digestData.withUnsafeMutableBytes { digestBytes in
            messageData.withUnsafeBytes { messageBytes in
                CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
            }
        }

A slight change to this code caused some error such as the compiler seems to think I want the old deprecated function, but returning a value does not seem to be mandatory.

You're right. This does compile without the warning. The only difference between what you wrote and my original code is the single line in the body of the inner block.


I don't really understand what's going on here, but I'd like to. CC_MD5 is an old C function, so it doesn't have multiple definitions. It always accepts the same parameters and returns the same kind of value.


It appears that the data types of digestBytes and messageBytes are different depending on which expression is used. If I put both the new and old expressions inside the block, in either order, the old expression generates a compiler error: "Cannot convert value of type UnsafeRawBufferPointer to expected UnsafeRawPointer?. But if I put just the old expression, it works, although with warnings.


What is Swift doing here? How is it deciding what types to make each variable?

What is Swift doing here? How is it deciding what types to make each variable?


The details of type inference in Swift is not clearly documented and hard to predict, especially when closure is involved.


One thiing sure is that you can explicity annotate types if you want to avoid such unpredictable behaviours...

        digestData.withUnsafeMutableBytes { (digestBytes: UnsafeMutableRawBufferPointer)->Void in
            messageData.withUnsafeBytes { (messageBytes: UnsafeRawBufferPointer)->Void in
                CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
            }
        }

This came up recently on Swift Forums. Check out this thread, which has a bunch of good advice.

In this case I’m going to specifically recommend the code that I posted there. Using an array for the digest makes sense because your final goal is to create a hex representation (

md5Hex
) and thus there’s no reason to use any of the alternative techniques. These alternatives have benefits (like avoiding an extra allocation), but they won’t benefit you.

OOPer wrote:

The details of type inference in Swift is not clearly documented and hard to predict, especially when closure is involved.

Indeed. In this specific case, however, the difference in behaviour is tied to a well-known Swift oddity. To understand this, you must first realise that Swift 5 introduced a new API on

Data
that uses the
Unsafe[Mutable]RawBufferPointer
. So now you have two methods in play:
func withUnsafe[Mutable]Bytes<ResultType, ContentType>(_ body: (Unsafe[Mutable]Pointer<ContentType>) throws -> ResultType) rethrows -> ResultType
func withUnsafe[Mutable]Bytes<ResultType>             (_ body: (Unsafe[Mutable]RawBufferPointer    ) throws -> ResultType) rethrows -> ResultType

with the first one being deprecated.

Now consider this code snippet:

digestData.withUnsafeMutableBytes { digestBytes -> Void in
//        ^
// 'withUnsafeMutableBytes' is deprecated: use `withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R` instead
    messageData.withUnsafeBytes { messageBytes in
        _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes)
    }
}

Both closures are a single statement, and Swift will do type inferencing ‘through’ a single-statement closure. Thus, it can use the type of the third parameter of

CC_MD5
to infer the type of
digestBytes
as an
UnsafeMutablePointer<UInt8>
. That does not match
Unsafe[Mutable]RawBufferPointer
, so you end up using the original deprecated API, and hence the deprecation warning.

Contrast this with this snippet:

digestData.withUnsafeMutableBytes { digestBytes -> Void in
    messageData.withUnsafeBytes { messageBytes in
        _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes)
        //                                                               ^
        // Cannot convert value of type 'UnsafeMutableRawBufferPointer' to expected argument type 'UnsafeMutablePointer<UInt8>?'
        print("Hello Cruel World")
    }
}

All I’ve done is add a call to

print
in the inner closure. This makes the closure a multi-statement closure, which disables type inferencing. Swift then chooses the
Unsafe[Mutable]RawBufferPointer
method, resulting in the type mismatch error.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Depending on what you're doing, you could easily convert your code in something like that:


  _ = d.withUnsafeBytes({ (body: UnsafeRawBufferPointer) -> Void in
  CC_MD5(body.baseAddress, CC_LONG(self.count), &digest)
  })


Excuse me.

This is my code:


// 'withUnsafeMutableBytes' is deprecated: use 'withUnsafeMytableBytes(_:(UnsafeMutableRawBufferPointer) throws -> R) rethrows -> 
// R'  instead
var data = Data(count: length)
let result data.withUnsafeMutableBytes {
     return SecRandomCopyBytes(kSecRandomDefault, length, $0)
}

What should I do to fix this warning?

I have no idear, Please.

You should better start a new thread for your own. Not many would have attention to a new comment of an old thread.

I am so sorry about Abuse Reported.

My English is terrible, I don't know what mean of 'Abuse Reported' before

I think that is repley, sorry~

I am so sorry, again, and I am a new guy.

I can't understand of start a new thread, or could I say I don't know how to write code.

I need codes, please

This actually helped me! Adding `bindMemory(to: UInt8.self).baseAddress` to the `digestBytes` saved my day! Thanks!