In Swift, although we try to provide a sound, total type system as the default environment for the most part, we provide reasonably easy explicit ways to poke holes where the type system isn't a perfect fit, such as !
for unwrapping optionals known to never be nil, as!
for casting to a type known to match, and try!
for ending error propagation when errors are known to never to be possible. However, one place we don't provide a good type system escape hatch is with class initializers and inheritance. If a class conforms to a protocol, all of its subclasses must in order to be substitutable. If that protocol requires initializers, that initializer implementation therefore must be required
of every subclass. This is necessary for the soundness of the type system, but oftentimes it doesn't match the reality of the API expectations. In the best case, this mismatch leads to boilerplate trap implementations; for example, UIView
conforms to NSCoding
in order to be usable with Interface Builder, but in practice user-defined views are often never used in nibs or storyboards; these view classes nonetheless must declare a stub implementation of init(coder:)
:
class MyView: UIView {
required init(coder: NSCoder) {
fatalError("i don't want this and will never call it")
}
}
In the worst case, this severely limits the expressivity of non-final classes, since you fundamentally can't retroactively add required initializers to classes to make them conform to new protocols:
extension NSURL: ExpressibleByStringLiteral {
required init(stringLiteral: String) { // error: can't retroactively require this of all subclasses
self.init(string: stringLiteral)!
}
}
In many of these cases, the operation is not really used in practice on all subclasses; things like coding on views are often not exercised in practice on all view implementations, and convenience initializer interfaces on class clusters like NSURL
or NSString
are not generally expected to work on an arbitrary concrete implementation. In the spirit of Swift's other escape hatches, it'd be nice to have a way to explicitly say "I know this initializer is formally required
but I'm OK with it only working on specific classes that implement it." As a strawman for want of a better syntax, we could spell this as required!
:
protocol FantasyCoding {
init(coder: FantasyCoder)
}
class FantasyView: FantasyCoding {
required! init(coder: FantasyCoder) { }
}
class MyView: FantasyView {
var x, y, z: Int
// ok not to override init(coder:) here
}
extension FantasyURL: ExpressibleByStringLiteral {
// OK to add a required! initializer in an extension
required! init(stringLiteral: String) { .. }
}
A required!
initializer would trap dynamically if invoked on a subclass that does not or cannot provide an override implementation.
Several years ago, I had proposed introducing non-inherited protocol conformances as another way of addressing some of these expressivity issues. However, that would complicate the type system, and in particular the already-complicated interactions between subclassing and protocol polymorphism, even further. Also, while that feature would address the "retroactively extending a class to conform to a protocol" use case, it wouldn't address other places where strict required
creates problems, such as within purely class hierarchies, or in cases where you do want some subclasses to still be able to selectively override protocol behavior, such as when there are multiple inherited class cluster roots as with NSString
and NSMutableString
. On the other hand, a feature like required!
would be a general escape hatch for when initializer inheritance is needed to satisfy the type system but not necessary in practice.