How does cString(using: .utf8) and withUnsafeFileSystemRepresentation(_:) differ from each other?

Consider the following code snippet:

let url = FileManager.default.homeDirectoryForCurrentUser

let cString1 = url.absoluteString.cString(using: .utf8)

let cString2 = url.withUnsafeFileSystemRepresentation { $0 }

Can we expect cString1 and cString2 to be equal C strings?

As described in the docs of withUnsafeFileSystemRepresentation(_:) it is converting the Swift string to a C string with UTF8 encoding. That's exactly the same as what cString(using: .utf8) is doing.

Both convert the Swift string to the type UnsafePointer<Int8>?.

Only Xcode is displaying the return type of cString(using:) as [CChar]? where CChar is a type alias for Int8. Also a Swift array can just passed to an UnsafePointer as far as I know.

Mostly I just use cString(using: .utf8) and everything is fine but there are rare cases where I need to use withUnsafeFileSystemRepresentation(_:) for some reason, otherwise the C function is not understanding the string.

So what is the difference I'm missing here?

1 answer

  • answered 2017-11-12 21:11 Martin R

    From the Apple File System Guide FAQ:

    To avoid introducing bugs in your code with mismatched Unicode normalization (for iOS 10.3.0, 10.3.1 and 10.3.2) in filenames, do the following:

    • Use high-level Foundation APIs such as NSFileManager and NSURL when interacting with the filesystem.
    • Use the fileSystemRepresentation property of NSURL objects when creating and opening files with lower-level filesystem APIs such as POSIX open(2), or when storing filenames externally from the filesystem.

    So withUnsafeFileSystemRepresentation() is needed if the path is passed to a POSIX call, such as open(), stat(), unlink(), getxattr(), ... and guarantees that the correct Unicode normalization form for the system call is created.

    Simple example (remove file if it exists, ignoring errors):

    url.withUnsafeFileSystemRepresentation {
        _ = unlink($0)
    }
    

    See Write extend file attributes swift example for a "real" example. Note that

    let cString2 = url.withUnsafeFileSystemRepresentation { $0 }
    

    is invalid because the C string is not valid outside the context of the block.

    Here is an example demonstrating that the two APIs can give different C strings:

    let path = "Café".precomposedStringWithCanonicalMapping
    let url = URL(fileURLWithPath: path)
    
    path.withCString { s1 in
        url.withUnsafeFileSystemRepresentation { s2 in
            print(strcmp(s1, s2) == 0) // false
        }
    }
    

    Using the Foundation APIs (FileManager, URL, ...) is recommended, and then you don't have to care about the file system representation.