Passing FILE to fopen

I have a C function cmark_node *cmark_parse_file(FILE *f, int options); to which I'd like to pass in a FILE handle. I currently have the following code

public func parse(file: URL, options: Options) throws -> Node? {
        var cFileHandle: UnsafeMutablePointer<FILE>?
        defer {
            if let cFile = cFileHandle {
                fclose(cFile)
            }
        }

        cFileHandle = try file.withUnsafeFileSystemRepresentation({ cFileUrl in
            if cFileUrl == nil {
                throw Errors.invalidFileUrl
            }

            return fopen(cFileUrl, "r")
        })

        if cFileHandle == nil {
            throw Errors.invalidFile
        }

        return cmark_parse_file(cFileHandle, options.rawValue)
            .map { Node(owned: $0) }
    }

Here, I'm trying to

  1. always close the file handle if opened
  2. Avoid passing a null pointer to fopen - if the URL cannot be represented by a file system representation

Is there a way to simplify this code? - primarily around this bit

 cFileHandle = try file.withUnsafeFileSystemRepresentation({ cFileUrl in
            if cFileUrl == nil {
                throw Errors.invalidFileUrl
            }

            return fopen(cFileUrl, "r")
        })

if cFileHandle == nil {
   throw Errors.invalidFile
}

For a one-off I’d write it like so:

func parse(url: URL) {
    guard let f = fopen(url.path, "r") else {
        … throw …
    }
    defer { fclose(f) }
    parse(file: f)
}

If I found myself doing this a lot, I’d write a wrapper that hides all the unsafe stuff.

extension URL {

    func withOpenCFile<Result>(_ body: (_ file: UnsafeMutablePointer<FILE>) -> Result) throws -> Result {
        guard let f = fopen(self.path, "r") else {
            … throw …
        }
        defer { fclose(f) }
        return body(f)
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

Thank you. I didn't realize Swift would automatically bridge a String to C, and, we could add the defer after the guard. This is a lot simpler :)

Does url.path give the file system representation of the URL? I assume it would return an empty string in the event the URL doesn't really map to a file on the system?

Does url.path give the file system representation of the URL?

Yes.

This assumes that this is a file system URL. If you want to check for that, add the following:

guard self.isFileURL else {
    … something …
}

The only potential gotcha here is with platforms that don’t use UTF-8 as their file system representation. That’s not an issue on Apple platforms or Linux. I’m less confident about Windows.

I assume it would return an empty string in the event the URL doesn't
really map to a file on the system?

Nope.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like