Swift Strangeness? Failable initializer overriding non-failable vs. default parameters


#1

Hi all,

recently I stumbled upon an interesting and slightly confusing aspect of our favorite language.

I had defined a Cache class to conform to NSCoding and inherit from NSObject. My class’s initializer was declared init?(entries: [Entry]? = nil) at first, giving an option to pre-populate it when called from the NSCoding-conformant initializer but also simply start from scratch in my program. Failability is a valuable feature since the class depends on external resources that could be unavailable. Of course there are other means to model this, but a failable initializer is the most elegant one, I think.

Later on, I decided to make the Entry class private to my Cache class.

Now I had to split the initializer in a private init?(entries: [Entry]?) and an internal or public convenience init?().

I’ll abstract a bit now:

First version:

public class A: NSObject {
  public class X { }
  public init?(x: X? = nil) { }
}

— all good. I can use it like let a = A() in my program.

Second version:

public class B: NSObject {
  private class X { }
  private init?(x: X?) { }
  public convenience override init?() { self.init(x: nil) } // ERROR
}

Now the compiler complains "failable initializer 'init()' cannot override a non-failable initializer" with the overridden initializer being the public init() in NSObject. This is the same in Swift 2 and 3.
Omitting the override does not work, the compiler now says “overriding declaration requires an 'override’ keyword", so it does count as overriding anyway. Suggested Fix-it is inserting override, giving the other error.

How comes I can effectively declare an initializer A.init?() without the conflict but not B.init?() ?

And: Why at all am I not allowed to override a non-failable initializer with a failable one? The opposite is legal: I can override a failable initializer with a non-failable, which even requires using a forced super.init()! and thus introduces the risk of a runtime error. I always have a bad feeling when I use !, so I try to avoid it whenever possible. I would even accept to get a compiler warning :wink:

To me, letting the subclass have the failable initializer feels more natural since an extension of functionality introduces more chance of failure. But maybe I am missing something here – explanation greatly appreciated.

But I found a workaround. My class now looks like this:

public class C: NSObject {
  private class X { }
  private init?(x: X?) { }
  public convenience init?(_: Void) { self.init(x: nil) } // NO ERROR
}

Now I can use it like let c = C(()) or even let c = C() – which is exactly what I intended at first.

The fact that the new declaration does not generate an error message seems (kind of) legal since it (somehow) differs from the superclass initializer's signature. Nevertheless, using a nameless Void parameter at all and then even omitting it completely in the 2nd version of the call feels a bit strange… But the end justifies the means, I suppose. Elegance is almost preserved.

What do you think?

Sincerely,
Stefan

PS: I already posted this question on Stack Overflow <http://stackoverflow.com/q/38311365/1950945>, but for the more philosophical aspects / underpinnings, it is not the right medium — you are obliged to ask a concrete question there (How to?, not Why?).