Introducing Namespacing for Common Swift Error Scenarios
- Proposal: SE-00xx
- Authors: Dave DeLong
- Review Manager: TBD
- Status: TBD
- Decision notes: TBD
- Implementation: TBD
- Discussion threads: TBD
- Review thread: TBD
Introduction
This proposal introduces namespacing for fatal errors. It provides an umbrella for common exit scenarios, modernizing a holdover from C-like languages.
Motivation
Swift's fatalError is arguably insufficient for a multitude of real world uses including discoverability, extensibility, and significance. Swift lacks a modern extensible solution that differentiates fatal error scenarios. Too many developer libraries include variations of the following custom utility, which adds an annotated unimplemented exit point to code:
/// Handles unimplemented functionality with site-specific information
/// courtesy of Soroush Khanlou, who discovered it from Caleb Davenport,
/// who discovered it from Brian King
func unimplemented(_ function: String = #function, _ file: String = #file) -> Never {
fatalError("\(function) in \(file) has not been implemented")
}
Free-standing functions (including fatalError) are not easily discoverable to programmers new to Swift. By namespacing marker functions to a common type, they become groupable, extensible, and more discoverable in the IDE. This allows users to autocomplete and jump-to-definition.
Proposed solution
Introduce Fatal, a public struct with static error members. The precise wording and choice of each error message can be bikeshedded after acceptance of this proposal's namespacing concept.
/// An umbrella type supplying static members to handle common
/// and conventional exit scenarios.
public enum Fatal {
/// Die because a default method must be overriden by a
/// subtype or extension.
public static func mustOverride(function: StaticString = #function, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "Must be overridden", extra: String(describing: function), file: file, line: line)
}
/// Die because this code branch should be unreachable
public static func unreachable(_ why: String, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "Unreachable", extra: why, file: file, line: line)
}
/// Die because this method or function has not yet been implemented.
///
/// - Note: This name takes precedence over `unimplemented` as it
/// is clearer and more Swifty
public static func notImplemented(_ why: String? = nil, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "Not Implemented", extra: why, file: file, line: line)
}
/// Die because of a failed assertion. This does not distinguish
/// between logic conditions (as in `assert`) and user calling
/// requirements (as in `precondition`)
public static func require(_ why: String? = nil, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "Assertion failed", extra: why, file: file, line: line)
}
/// Die because this TODO item has not yet been implemented.
public static func TODO(_ reason: String? = nil, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "Not yet implemented", extra: reason, file: file, line: line)
}
/// Provide a `Fatal.error` equivalent to fatalError() to move
/// Swift standard style away from using `fatalError` directly
public static func error(_ reason: String? = nil, file: StaticString = #file, line: UInt = #line) -> Never {
die(reason: "", extra: reason, file: file, line: line)
}
/// Performs a diagnostic fatal error with reason and
/// context information.
private static func die(reason: String, extra: String?, file: StaticString, line: UInt) -> Never {
var message = reason
if let extra = extra {
message += ": \(extra)"
}
fatalError(message, file: file, line: line)
}
}
Source compatibility
This proposal is strictly additive. There is no reason to deprecate or remove fatalError.
Effect on ABI stability
This proposal does not affect ABI stability.
Effect on API resilience
This proposal does not affect API resilience.
Alternatives considered
-
Naming alternatives include
Die,Crash,Require,Abort,Halt. These alternatives, likeFatal, are chosen for their readability in source. -
Using
Neveras a namespace, such asNever.reachable,Never.TODO,Never.sayDie,Never.sayNeverAgain, etc. This is slightly less readable but avoids adding a new type./// The return type of functions that do not return normally; a type with no /// values. /// /// Use `Never` as the return type when declaring a closure, function, or /// method that unconditionally throws an error, traps, or otherwise does /// not terminate. /// /// func crashAndBurn() -> Never { /// fatalError("Something very, very bad happened") /// } public enum Never { } -
In terms of practical utility but limited dignity, the following may be of use:
extension Fatal { /// Silence Xcode requirements (like init(coder:)) /// by adding functionality that should never be called. public static func silenceXcode(function: StaticString = #function, file: StaticString = #file, line: UInt = #line) -> Never { die(reason: "Xcode requires unimplemented \(#function)", extra: nil, file: file, line: line) } }