The Never type in Swift allows you to tell the compiler about an exit point in your code. It’s a type with no values that prevents writing unuseful code by creating dead ends.
While the type Never on its own might be a little unknown, you might have been using it already in your codebase. Several default Swift methods make use of it, including Combine operators.
Understanding how Never works
Never is defined as a frozen caseless enum inside the standard Swift library:
@frozen enum Never { }
Never returning functions use this enum to communicate this fact to the compiler. Due to this, the compiler can avoid generating incorrect control flow diagnostics for conditions such as “function does not provide a return value” after a call to a never returning method. The same would apply for your own defined empty enums.
Swift 3 introduced the Never type as a substitute for the rarely used @noreturn
keyword as described in SE-102. The compiler handles caseless enums as so-called uninhabited types. Evaluated uninhabited types are considered unreachable by control flow diagnostics allowing for optimizations to apply.
Examples of Never in the standard library
Common examples of functions that do not return are exit
and fatalError
. Both methods will terminate the application without returning a value to the caller. You can use them to manually terminate the app if a restart of your app is required. A typical use case for me is to do this after enabling a specific debug mode:
func enableDebugMode() {
UserDefaults.standard.isDebugModeEnabled = true
exit(0)
}
At this point, the compiler doesn’t know that the enableDebugMode
will never return. In other words, the compiler will show a warning on the caller’s side:
The compiler throws an error to inform you about an unhandled case. However, we know that our debug enabling method will never return. Therefore, using Never as a return type will be beneficial:
/// - Returns: `false` if debug mode is already enabled, otherwise terminates the app.
func toggleDebugMode() -> Bool {
guard !UserDefaults.standard.isDebugModeEnabled else { return false }
enableDebugMode()
}
func enableDebugMode() -> Never {
UserDefaults.standard.isDebugModeEnabled = true
exit(0)
}
Practical Examples
While the above paragraph demonstrates a practical example, I believe there are even better examples to share. As described earlier, Combine is also using the Never type to eliminate code paths and optimize for readability. For instance, you could have a content cache defined as follows:
struct ContentCache {
/// Publishes new content items once added to the cache.
public var contentPublisher: AnyPublisher<Content, Never> {
contentPassthrough.eraseToAnyPublisher()
}
private var contentPassthrough = PassthroughSubject<Content, Never>()
func add(_ content: Content) {
contentPassthrough.send(content)
}
}
Never conforms to the Error protocol, allowing us to use it as an expected error type. Doing so tells the compiler there won’t be any errors in this path, allowing us to bypass handling errors:
let cancellable = cache.contentPublisher.sink { completion in
switch completion {
// Not required by the compiler since it knows there's not going to be a failure.
// case .failure:
// break
case .finished:
print("No more content expected")
}
} receiveValue: { newContent in
print("New content published: \(newContent.title)")
}
If you’re new to Combine, I encourage you to read my Getting started with the Combine framework in Swift article.
Using Never in combination with the Result enum
As you’ve learned in my article Result in Swift: Getting started with Code Examples, you can define a result enum with an expected success and failure type. In some cases, protocols force you to work with the result enum, even though you’re sure there will not be a failure path. In these cases, you can define a result enum with the Never error type and make its error an uninhabited type:
let result: Result<String, Never> = .success("Example of Never usage")
switch result {
/// Once again, not required by the compiler since it knows there's not going to be a failure.
/// case .failure:
/// break
case .success(let string):
print(string)
}
Conclusion
While the Never type isn’t used often by developers, it can still be helpful to benefit from its effects on the compiler. You’ll be able to remove code your app will never reach, resulting in more straightforward and readable code.
If you like to improve your Swift knowledge, check out the Swift category page. Feel free to contact me or tweet me on Twitter if you have any additional tips or feedback.
Thanks!