Working with C strings in Swift, or: How to convert UnsafePointer<CChar> to CString
Asked Answered
S

2

31

While playing with Standard C Library functions in Swift, I came across problems when passing C strings around. As a simple example (just to demonstrate the problem), the Standard C Library function

char * strdup(const char *s1);

is exposed to Swift as

func strdup(_: CString) -> UnsafePointer<CChar>

which means that the return value of strdup() cannot be passed to another strdup() call:

let s1 : CString = "abc"
let s2 = strdup(s1) // OK, s2 is a UnsafePointer<CChar>
let s3 = strdup(s2) // error: could not find an overload for '__conversion' that accepts the supplied arguments

My question is: How to create a Swift CString from a UnsafePointer<CChar>, so that the C string returned by one standard library function can be passed to another function?

The only way that I could find is (using code from How do you convert a String to a CString in the Swift Language?):

let s2a = String.fromCString(s2).bridgeToObjectiveC().UTF8String
let s3 = strdup(s2a)

But I do not find this satisfying for two reasons:

  • It is too complicated for a simple task.
  • (Main reason:) The above conversions works only if the C string is a valid UTF-8 string, otherwise it fails with a runtime exception. But a C string is an arbitrary sequence of characters, delimited by a NUL character.

Remarks/Background: Of course, high-level functions using high-level data structures like Swift String or Objective-C NSString are preferable. But there are BSD functions in the Standard C Library which do not have an exact counterpart in the Foundation frameworks.

I came across this problem while trying to answer Accessing temp directory in Swift. Here, mkdtemp() is a BSD function for which no exact NSFileManager replacement exists (as far as I know). mkdtemp() returns a UnsafePointer<CChar> which has to be passed to the NSFileManager function stringWithFileSystemRepresentation which takes a CString argument.


Update: As of Xcode 6 beta 6, this problem does not exist anymore because the mapping of C-Strings into Swift has been simplified. You can just write

let s1 = "abc"      // String
let s2 = strdup(s1) // UnsafeMutablePointer<Int8>
let s3 = strdup(s2) // UnsafeMutablePointer<Int8>
let s4 = String.fromCString(s3) // String
Shiest answered 25/6, 2014 at 21:32 Comment(1)
Thanks to the anonymous downvoter who reminded me to bring the question up-to-date :)Shiest
G
19

Swift 1.1 (or perhaps earlier) has even better C string bridging:

let haystack = "This is a simple string"
let needle = "simple"
let result = String.fromCString(strstr(haystack, needle))

The CString type is gone completely.

Gambrinus answered 26/6, 2014 at 19:32 Comment(3)
Hi Nate, thanks for your reply. st.withCString({ $0 }) is definitely more elegant than bridgeToObjectiveC().UTF8String. - But String.fromCString(up) already fails with a runtime exception if up is not a valid UTF-8 string, so this issue it not solved. Try CStringFromUnsafeCChar(foo()) where foo() is a C function returning a C string which is not valid UTF-8. For example: char *foo(void) { static char buf[] = { -1, 0}; return buf; } .Shiest
Well, drat. Bug report time -- it shouldn't be a one-way conversion from CString to UnsafePointer<CChar>.Gambrinus
reinterpretCast() works perfectly, I could apply it here: https://mcmap.net/q/49991/-accessing-temp-directory-in-swift. Thanks again!Shiest
T
6

The String struct in Swift has an init routine you can use like:

let myString = String(cString: myUnsafePointer)

see also init(cString: UnsafePointer)

Tanganyika answered 26/1, 2020 at 23:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.