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.)
@ericasadun How about this? pic.twitter.com/1ijQD5uN3j
— Kametrixom (@Kametrixom) August 27, 2015
Here’s a link to a gist where I’ve thrown up some of this all together.
One Comment
[…] by Erica Sadun’s post on the matter, we also replaced many uses of print() with the following […]