Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?

Tags:

swift

I noticed some unusual behaviour when working with a C library which took strings in as const char * (which is converted to Swift as UnsafePointer<Int8>!); passing a String worked as expected, but a String? seemed to corrupt the input. 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, printing a null-terminated list 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:

let input: String? = "Hello"

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

The C library's function allows NULL in place of a string, and I sometimes want to pass that in in Swift as well, so it makes sense for the input string to be an optional.

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

Edit

It appears to have something to do with multiple arguments. The 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 h r
0x101003170: T h r
0x101003170: T h r
0x101003170: T h r

It appears that there's a bug with how Swift handles multiple arguments.

like image 693
Robert Avatar asked Nov 09 '22 08:11

Robert


1 Answers

I didn't find anything useful on if this is desired behaviour or just a bug.

The pragmatic solution would probably be to just have a proxy method like this, but you probably did something similar already.

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

proxy(input, test)

Did you test if it was working in Swift 2? They changed something maybe related in Swift 3:

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

like image 59
retendo Avatar answered Nov 15 '22 07:11

retendo