Initializer optionality


#1

Hi there,

Yesterday, I learned I couldn't add init?() to a subclass of NSObject, and realized that init?() and init() are considered as conflicting. Looking at some Sema code to learn a bit more, apparently it was a conscious decision to ignore optionality when it comes to initializer. But... why?

class Foo {
  func bar() -> Int { return 0 }
  func bar() -> Int? { return 0 }  // OK
  init() {}
  init?() {}  // invalid redeclaration of 'init()'
}

This appears weird to me syntactically speaking. I understand that failable initializer is tricky, but is this due to technical difficulties or something? If so, is it posssible to change this behavior in the future version?


(Jordan Rose) #2

The explanation is that at the call site, differentiating between the two initializers would be very subtle—you'd either have to explicitly write Foo() as Foo? or Foo as Optional, or use it in an optional context: if let foo = Foo(). This argument could apply to functions too, but since initializer optionality is handled specially (it's part of the keyword) it was easier to check for. The function overloads probably aren't a good idea either in most cases.

Separately, init?() specifically probably isn't a good idea, because there's nothing the developer can do if it fails. They don't have any information about why it failed, and they can't have passed invalid parameters or something because there are no parameters.


#3

I agree on that perhaps it's practically not the best idea, but the same logic can apply to the bar() functions there, no? And the language currenty allows it unless it's initializer, which appears weird to me.


#4

Does this imply that if it was easier to check if functions were overloaded on return type then that may have been banned? Or only for return types that only differ in optionality? Or is it because failable initialisers were added later so the restriction was able to be made without breaking source compatibility? I agree that it isn't a great idea in many cases, but banning it only for initialisers leaves an unappealing inconsistency.


(Jordan Rose) #5

I'm not sure. Failable initializers were definitely implemented after other function overloading, but it was a long time ago and I don't remember the reasoning. It's certainly technically feasible to lift the restriction on initializers if someone wants to take that through the evolution process.


(Alexander Momchilov) #6

Even if init?() were possible, it would be a code smell. Clearly, whether or not the initializer fails on some kind of state, and this state is external to the instance in some sense (whether it be a reference to a global, another class, some static members, etc.). Such an object would be hard to mock and test. It's better to inject the dependancy explicitly via an argument to the initializer.


#7

Before throwable errors, I would have argued that a class representing a resource might need to be failable if the resource could not be acquired or created. I agree that any such usage today would be a code smell.


#8

Sure, it's hard to think of any good use at all for init?() as you and @jrose point out. But this thread is really about a wider inconsistency, which applies to initialisers that take arguments:

struct S {
  init?(_ i: Int) { }
  init(_ i: Int) { } // error: invalid redeclaration of 'init'
}

It's not important enough to me personally to propose a change, which is why I phrased it as an “unappealing inconsistency” rather than a practical issue.