Building assert() in Swift, Part 2: __FILE__ and __LINE__

Two occasionally useful features of C are the __FILE__ and __LINE__ magic macros. These are built into the preprocessor, and expanded out before the C parser is run. Despite not having a preprocessor, Swift provides very similar functionality with similar names, but Swift works quite differently under the covers.

Built-In Identifiers

As described in the Swift programming guide, Swift has a number of built-in identifiers, including __FILE__, __LINE__, __COLUMN__, and __FUNCTION__. These expressions can be used anywhere and are expanded by the parser to string or integer literals that correspond to the current location in the source code. This is incredibly useful for manual logging, e.g. to print out the current position before quitting.

However, this doesn’t help us in our quest to implement assert(). If we defined assert like this:

func assert(predicate : @autoclosure () -> Bool) { 
	#if DEBUG
		if !predicate() {
			println("assertion failed at \(__FILE__):\(__LINE__)")
			abort()
		}
	#endif
}

The above code would print out of the file/line location that implements assert() itself, not the location from the caller. That isn’t helpful.

Getting the location of a caller

Swift borrows a clever feature from the D language: these identifiers expand to the location of the caller when evaluated in a default argument list. To enable this behavior, the assert() function is defined something like this:

func assert(condition: @autoclosure () -> Bool, _ message: String = "",
	file: String = __FILE__, line: Int = __LINE__) {
		#if DEBUG
			if !condition() {
				println("assertion failed at \(file):\(line): \(message)")
				abort()
			}
		#endif
}

The second parameter to the Swift assert() function is an optional string that you can specify, and the third and forth arguments are defaulted to be the position in the caller’s context. This allows assert() to pick up the source location of the caller by default, and if you want to define your own abstractions on top of assert, you can pass down locations from its caller. As a trivial example, you could define a function that logs and asserts like this:

func logAndAssert(condition: @autoclosure () -> Bool, _ message: StaticString = "",
	file: StaticString = __FILE__, line: UWord = __LINE__) {

	logMessage(message)
	assert(condition, message, file: file, line: line)
}

This properly propagates the file/line location of the logAndAssert() caller down to the implementation of assert(). Note that StaticString, as shown in the code above, is a simple String-like type used to store a string literal, such as one produced by __FILE__, with no memory-management overhead.

In addition to being useful for assert(), this functionality is used in the Swift implementation of the higher-level XCTest framework, and may be useful for your own libraries as well.

All Blog Posts