Capturing context #swiftlang

Say you’re working with a type and you want to throw an error that reflects the context of where it originates. You can do that, mostly, using a few built-in compiler keywords. __FUNCTION__, __LINE__, and __FILE__ provide literal interpolation about the details of a calling function:

public struct Error: ErrorType {
    let source: String; let reason: String
    public init(_ reason: String, source: String = __FUNCTION__,
        file: String = __FILE__, line: Int = __LINE__) {
            self.reason = reason; self.source = "\(source):\(file):\(line)"
    }
}

A typical line of output for Error looks like this example:

Error(source: "myFunction():<EXPR>:14", reason: "An important reason")

While this struct enables you to capture the error’s function, file, and line, you can’t capture the originating parent type without a type parameter. To fetch that type, extend the Error initializer to include a “source type”, and pass self.dynamicType from the constructor.

public struct Error: ErrorType {
    let source: String; let reason: String
    public init(_ reason: String, type: Any = "", 
        source: String = __FUNCTION__,
        file: String = __FILE__, 
        line: Int = __LINE__) {
            self.reason = reason; self.source = "\(source):\(file):\(line):\(type)"
    }
}

I find the extra type parameter deeply annoying. The entire point of this approach is to simplify error generation.

public struct Parent {
    func myFunction() throws {
        throw Error("An important reason", type: self.dynamicType)}
}

do {try Parent().myFunction()} catch{print(error)}
// Error(source: "myFunction():<EXPR>:14:Parent", reason: "An important reason")

Use a protocol to automatically pick up on type context. The default implementation in the following Contextualizable extension refers to self.dynamicType in a way that a method signature cannot.

protocol Contextualizable {}
extension Contextualizable {
    func currentContext(file : String = __FILE__, function : String = __FUNCTION__, line : Int = __LINE__) -> String {
        return "\(file):\(function):\(line):\(self.dynamicType)"
    }
}

Combining approaches enables you to simplify the whole shebang. The shared Error type reduces to simple lets and context responsibility moves from the Error initializer to a conforming type, automatically inheriting the currentContext method.

public struct Error: ErrorType {
    let source: String; let reason: String
    public init(_ source: String = __FILE__, _ reason: String) {
        self.reason = reason; self.source = source
    }
}
public struct Parent: Contextualizable {
    func myFunction() throws {
        throw Error(currentContext(), "An important reason")}

The updated results now includes the originating type in the string output.

As reader Kametrixom points out,  you can also extend Contextualizable to construct an error on your behalf. (He’s also written a really nice error type that optionally adds context.)

Here’s a link to a gist where I’ve thrown up some of this all together.

One Comment