Load text file into an array in Swift I'm drowning in Swift optionals and error handling syntax. I'm just converting a text file into an array of Strings. One would think that this could be a simple two or three liner but by the time I got it to work, I ended up with the following mess:

enum ImportError: ErrorType {
    case FileNotFound
    case CouldntGetArray
}

private func getLineArrayFromFile(fileName: String) throws -&gt; Array&lt;String&gt; {

    let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil)
    if path == nil {
        throw ImportError.FileNotFound
    }

    var lineArray: Array&lt;String&gt;?
    do {

        let content = try String(contentsOfFile: path!, encoding: NSUTF8StringEncoding)
        lineArray = content.componentsSeparatedByString("\n")

    }catch{
        throw ImportError.CouldntGetArray
    }

    return lineArray!
}


I actually don't really care about the using ErrorType enum, but I wanted to play around with the new Swift Error Handling syntax. 

I thought I understood optionals before, but they were giving me a headache when combined with the do-try-catch statement. I also didn't know if I should return an Array or an Optional Array.

What are the best practices for a situation like this? 


Error handling
Treatment of optionals
Code brevity/readability
func getLineArrayFromFile(fileName: String) throws -&gt; Array&lt;String&gt;




The function does not get lines from an arbitrary file, but from a resource file.
The "get" prefix is usually not used in Objective-C or Swift.
Array&lt;String&gt; can be shortened to [String].


So my suggestion for the function name would be

func linesFromResource(fileName: String) throws -&gt; [String]



let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil)
if path == nil {
    throw ImportError.FileNotFound
}




As you are obviously using Swift 2, this can be simplified with the guard 
statement. Note that path is no longer an optional.
There are pre-defined NSError codes which can be used here instead of
defining your own. This also gives better error descriptions for free.


Example:

guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil) else {
    throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: [ NSFilePathErrorKey : fileName ])
}



do {
    let content = try String(contentsOfFile: path!, encoding: NSUTF8StringEncoding)
    lineArray = content.componentsSeparatedByString("\n")
} catch {
    throw ImportError.CouldntGetArray
}



You are catching the error and throw your own error code in the failure case,
so the actual error information is lost.
Better just call the try String(..) and let an error propagate to the
caller of your function:

let content = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)


Again, this gives better error descriptions for free.

So the complete method would now look like this (no optionals anymore,
no forced unwrapping with !):

func linesFromResource(fileName: String) throws -&gt; [String] {

    guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil) else {
        throw NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: [ NSFilePathErrorKey : fileName ])
    }
    let content = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
    return content.componentsSeparatedByString("\n")
}


And a typical usage would be

do {
    let lines = try linesFromResource("file.txt")
    print(lines)
} catch let error as NSError {
    print(error.localizedDescription)
} catch let error {
    print(error)
}


The reason for the final catch let error is that it is required that the catch statements are exhaustive.
Even if we know that the function throws only NSErrors, the compiler doesn't know
that. (There are exceptions but that is a different topic .)

Now to your question how an error should be handled in general, and I would 
say: it depends. There are tree different scenarios:


Loading the strings can fail, and you want to present or log an error message
in that case. Then the above method of throwing and catching an error is appropriate.
Loading the strings can fail, but the particular reason is of no interest
to the caller. In that case I would change the function to return an optional.


Example:

func linesFromResource(fileName: String) -&gt; [String]? {

    guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil) else {
        return nil
    }
    do {
        let content = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
        return content.componentsSeparatedByString("\n")
    } catch {
        return nil
    }
}

 // Usage:
if let lines = linesFromResource("file.txt") {
    print(lines)
}



Finally, if failing to load the strings is a programming error then the function should abort in
the error case, and return the (non-optional) strings otherwise.


As an example, if this function is only used to load strings from fixed compiled-in
resource files which are supposed to exist, then failing to load a file would
be a programming error and should be detected early:

func linesFromResource(fileName: String) -&gt; [String] {

    guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil) else {
        fatalError("Resource file for \(fileName) not found.")
    }
    do {
        let content = try String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
        return content.componentsSeparatedByString("\n")
    } catch let error {
        fatalError("Could not load strings from \(path): \(error).")
    }
}

let lines = linesFromResource("file.txt")
print(lines)


fatalError() prints a message before stopping execution of the program, which can
be helpful to locate the programming error. Otherwise you could shorten the
"forced" version to

func linesFromResourceForced(fileName: String) -&gt; [String] {

    let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil)!
    let content = try! String(contentsOfFile: path, encoding: NSUTF8StringEncoding)
    return content.componentsSeparatedByString("\n")
}


which is the "three liner" that you were looking for in the introduction to
your question.