Foundation file errors

So the following appears to be a way to open a file for i-o and write to it:

import Foundation

let s = "Test"
let filePath = "filefile.txt"
let u = URL(fileURLWithPath: filePath)

if let d = s.data(using: .utf8) {
    do {
        let fh1 = try FileHandle(forUpdating: u)
        defer { fh1.closeFile() }
        print(fh1)
        fh1.write(d)
    }
    catch {
        switch error {
        case let e as CocoaError:
            print("CocoaError: \(e)")
        case let e as NSError:
            print("NSError: \(e)")
            if let reason = e.localizedFailureReason {
                print("Reason: \(reason)")
            }
            print("Domain: \(e.domain)")
            print("Code: \(e.code)")
            print("User Info: \(e.userInfo)")
        default:
            print("other error")
        }
        fatalError("Error opening file")
    }
}
else {
    print("String conversion failed")
}

At first I only had the catch contain:

catch {
    print("Error: \(e)")
    fatalError("Error opening file")
}

That only gave me the not terribly useful "Error: The operation could not be completed".

I tried researching using the official Apple documentation and it was less than useful in helping me determine the issue. So I searched the Swift github repository and was able to back myself in to at least seeing it's an "NSError". From there I found the domain, code and userInfo properties. So now I have the above, which gives me:

NSError: The operation could not be completed
Domain: NSCocoaErrorDomain
Code: 4
User Info: ["NSURL": <CFURL 0x60ff10 [0x7ff260dfca98]>{string = filefile.txt, encoding = 134217984
        base = <CFURL 0x613de0 [0x7ff260dfca98]>{string = file:///mnt/d/src/swift/, encoding = 134217984, base = (null)}}]
Fatal error: Error opening file: file filehandle.swift, line 29

In the end, looking at FileHandle.swift I see this:

throw _NSErrorWithErrno(errno, reading: reading, url: url)

This leads me to FoundationErrors.swift, where func _NSErrorWithErrno is defined. In the end I find NSError.swift which contains POSIX error codes. It looks like 4 would be EIO, though I'm not really sure on that.

Anyway, this is all a long way of saying that, though I know in the end that the cause of the error is the fact that the file I am trying to open does not in fact exist, if I didn't know that I might have a hell of a time making that determination. Why is this so convoluted? Certainly it should not require me to read the run-time source code to determine the cause of an error... Am I missing something simple?

All of this is on the Windows 10 Ubuntu Linux subsystem, by the way, but I don't think that's particularly relevant.

It’s at least partly due to the age of the FileHandle APIs (I believe they go back to OpenStep) and needing to match the open source implementation to Apple’s. Some of it also seems to be poor error descriptions in the bridging, which could be fixable if the changes don’t deviate too far from Apple’s version.

IMO, stuff like this is why I consider Foundation to be a dead framework best positioned as a porting solution and not something that should ever be used in Swift otherwise.

3 Likes

I can agree with that. Is anyone out there writing more modern APIs to replace Foundation? And will they at some point be official parts of Swift?

Hmm! I decided to try this on my Mac (which I finally upgraded to High Sierra) and I get the following results:

CocoaError: CocoaError(_nsError: Error Domain=NSCocoaErrorDomain Code=4 "The file “filefile.txt” doesn’t exist." UserInfo={NSFilePath=/Users/Frank/src/swift/misc/misc/filefile.txt, NSUnderlyingError=0x7fcfc4d0ef90 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}})
Fatal error: Error opening file: file main.swift, line 43
Illegal instruction: 4

The first difference to note is that on Mac, the error is caught as CocoaError, whereas on Linux its not caught until NSError. The second thing to notice is that on Mac there is actual useful information about the error! So it appears that the Linux Foundation implementation only is what's lacking. :frowning:

For whatever its worth, I expanded the "CocoaError" case as follows:

    case let e as CocoaError:
        print("CocoaError [\(e.code)]: \(e.localizedDescription)")
        if let err = e.underlying {
            print(err.localizedDescription)
        }
        if let path = e.filePath {
            print("File path: \(path)")
        }
        if let url = e.url {
            print("URL: \(url)")
        }
        if let encoding = e.stringEncoding {
            print("String encoding: \(encoding)")
        }

and I get the follow (again on Mac, not Linux):

CocoaError [Code(rawValue: 4)]: The file “filefile.txt” doesn’t exist.
The operation couldn’t be completed. No such file or directory
File path: /Users/Frank/src/swift/misc/misc/filefile.txt
Fatal error: Error opening file: file main.swift, line 57

Much more useful...

There are a number of open source projects out there that make working with files in swift much more convenient, although almost all of them are built as wrappers around the Foundation APIs and so their performance/stability on Linux has varying results.

  • FileKit has the most forks/stars with a nice clean API interface, but it doesn't support Linux last I checked.
  • PathKit has about half as many forks/stars as FileKit and IMO is probably the easiest to learn/use (not as complex, nor as full featured as FileKit) and it also supports Linux.
  • FileSmith is newer and not very popular, but is strongly typed (file paths are different than directory paths). Supports Linux.
  • TrailBlazer is new and I'm working on it myself (so I'm a bit biased). Like FileSmith, it's strongly typed and as far as I know it's the only path library that isn't just a wrapper around the Foundation APIs (I'm using C APIs for everything since stable/strong Linux support is important for the work I do). Primarily developed on/for Linux, but every few weeks I run the tests on macOS and bring everything up-to-snuff for Darwin platforms. I've only started working on it a couple of months ago, so it's definitely lacking some features that the other libraries have and it's still in beta (but I'm making good progress on it).
1 Like

This is actually very relevant information since Foundation on Linux is definitely not as stable nor as consistent as it is on all Apple platforms.

I was not aware that the Foundation file management APIs were that old...but I'm also not terribly surprised by it.

You're not the only one who thinks this way. I find it very frustrating that Foundation functions so differently on Linux vs Darwin and it's exactly why I spend a significant amount of time trying to write cross-platform compatible libraries around C APIs (since many C APIs are more or less the same on Linux and Darwin). Not everyone has the time/patience to learn the C APIs (I read a lot of man pages), let alone write swift wrappers around them. I wish more effort was going into updating the Foundation APIs, especially since swift is supposed to take over the world. ;)