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
Never
as 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) } }