Why does Swift return an unexpected pointer when converting an optional string to UnsafePointer?

I noticed unusual behavior when working with the C library, into which strings were taken in the form of const char * (which is converted to Swift as UnsafePointer<Int8>! ); passing the String worked as expected, but a String? seemed to distort the entrance. Consider the test I wrote:

 func test(_ input: UnsafePointer<UInt8>?) { if let string = input { print(string[0], string[1], string[2], string[3], string[4], string[5]) } else { print("nil") } } let input: String = "Hello" test(input) 

This works as expected by printing a list of zero byte lines of UTF-8 bytes for the input string: 72 101 108 108 111 0

However, if I change the input to an optional string, so that it becomes the following:

 let input: String? = "Hello" 

I get a completely different set of values ​​as a result ( 176 39 78 23 1 0 ), although I would expect it to be the same. Passing to nil works as expected.

The C library function allows NULL instead of a string, and I sometimes want to pass it also to Swift, so it makes sense that the input string is optional.

Is this a bug in Swift or is Swift not designed to handle this case? Anyway, what's the best way to handle this?

Edit

This seems to be due to several arguments. C function:

 void multiString(const char *arg0, const char *arg1, const char *arg2, const char *arg3) { printf("%p: %c %c %c\n", arg0, arg0[0], arg0[1], arg0[2]); printf("%p: %c %c %c\n", arg1, arg1[0], arg1[1], arg1[2]); printf("%p: %c %c %c\n", arg2, arg2[0], arg2[1], arg2[2]); printf("%p: %c %c %c\n", arg3, arg3[0], arg3[1], arg3[2]); } 

Swift:

 let input0: String? = "Zero" let input1: String? = "One" let input2: String? = "Two" let input3: String? = "Three" multiString(input0, input1, input2, input3) 

Results in:

 0x101003170: T hr 0x101003170: T hr 0x101003170: T hr 0x101003170: T hr 

There seems to be an error with the way Swift handles multiple arguments.

+5
source share
3 answers

I did not find anything useful if this is the desired behavior or just a mistake.

A pragmatic solution would probably be to just have such a proxy method, but you probably already did something similar.

 func proxy(_ str: String?, _ functionToProxy: (UnsafePointer<UInt8>?) -> ()) { if let str = str { functionToProxy(str) } else { functionToProxy(nil) } } proxy(input, test) 

Have you tested if they worked in Swift 2? They changed something, possibly related to Swift 3:

https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md

+1
source

To be clear, there is a workaround until Apple fixes this. Expand your optional lines before passing them, and everything will work fine.

 var anOptional: String? var anotherOptional: String? func mySwiftFunc() { let unwrappedA = anOptional! let unwrappedB = anotherOptional! myCStringFunc(unwrappedA, unwrappedB) } 
0
source

As mentioned in the comments, this is a blatant bug in Swift.

Here is the workaround I am using. If you cannot trust Swift to convert strings to pointers for you, then you must do it yourself.

Assuming a function C defined as:

 void multiString(const char *arg0, const char *arg1, const char *arg2); 

Quick code:

 func callCFunction(arg0: String?, arg1: String?, arg2: String?) { let dArg0 = arg0?.data(using: .utf8) as NSData? let pArg0 = dArg0?.bytes.assumingMemoryBound(to: Int8.self) let dArg1 = arg1?.data(using: .utf8) as NSData? let pArg1 = dArg1?.bytes.assumingMemoryBound(to: Int8.self) let dArg2 = arg2?.data(using: .utf8) as NSData? let pArg2 = dArg2?.bytes.assumingMemoryBound(to: Int8.self) multiString(pArg1, pArg2, pArg3) } 

Attention:

Resist the temptation to put this in a function like:

 /* DO NOT USE -- BAD CODE */ func ocstr(_ str: String?) -> UnsafePointer<Int8>? { guard let str = str else { return nil } let nsd = str.data(using: .utf8)! as NSData //This pointer is invalid on return: return nsd.bytes.assumingMemoryBound(to: Int8.self) } 

which will remove the duplicate code. This does not work because the nsd data object is freed at the end of the function. Therefore, the pointer is invalid when returning.

0
source

Source: https://habr.com/ru/post/1257580/


All Articles