Swift - C API bridge - how to handle null pointers

Viewed 1543

In Swift I am using C API that returns struct with char array (containing UTF8 null terminated string or null).

struct TextStruct {
   char * text;
   //other data
}

I use:

let text: String = String(cString: data.text)

This works, however, when data.text is nullptr, this fails with

fatal error: unexpectedly found nil while unwrapping an Optional value

Is there any workaround, or I have to check data.text manually before using cString ctor?

3 Answers

In addition to Gwendal Roué's solution: You can annotate the C API to indicate whether the pointer can be null or not. For example,

struct TextStruct {
    char * _Nullable text;
    //other data
};

is imported to Swift as

public struct TextStruct {
    public var text: UnsafeMutablePointer<Int8>?
    // ...
}

where var text is a "strong" optional instead of an implicitly unwrapped optional. Then

let text = String(cString: data.text)
// value of optional type 'UnsafeMutablePointer<Int8>?' not unwrapped; ...

no longer compiles, and forces you to use optional binding or other unwrapping techniques, and the "fatal error: unexpectedly found nil" cannot happen anymore accidentally.

For more information, see "Nullability and Objective-C" from the Swift Blog – despite the title, it can be used with pure C as well.

Yes, you have to check data.text, in order to make sure it can feed the String(cString:) initializer, which is documented to require a non-null pointer.

A technique is to use a if let statement. This is a classic technique for safely unwrapping optional values:

let str: String?
if let ptr = ptr {
    str = String(cString: ptr)
} else {
    str = nil
}
print("got \(str ?? "nil")")

Another technique is the Optional.map function:

let str = ptr.map { String(cString: $0) }
print("got \(str ?? "nil")")

You can check if the address of the pointer is set

let textIsSet = Int(bitPattern: data.text) != 0

let text: String? = textIsSet ? String(cString: data.text) : nil
Related