Proposal: allow delegating from a throwing initializer to a failable initializer


(Matthew Johnson) #1

It is not currently possible to call a failable (optional) initializer from a throwing initializer. This is unfortunate as many Cocoa initializers are failable and it is desirable to be able to extend Cocoa types with throwing convenience initializers that call a failable (optional) designated initializer.

For example, something similar to the following should be possible:

enum CustomError: ErrorType { case E }

struct S {
    let urlString: String
}

extension NSURL {
    convenience init(s: S) throws {
        guard self.init(string: s.urlString)
            else { throw CustomError.E }
    }
}

In the current language, even if the compiler preventing us from calling the failable initializer we would not be able to access the return value in the guard expression. Furthermore, even if we could an Optional expression would not be valid in a guard condition. I am interested in ideas for how this should be handled. Would there be a special case of the guard statement to allow this? Or should some other mechanism be used to guarantee that we either successfully complete initialization or throw?

As an aside, the term ‘failable initializer’ implies to me any initializer that can fail, whether by returning an optional or by throwing. I think it would be best if we adopt more precise terminology to refer to ‘optional initializers’ and ‘throwing initializers’. I welcome any feedback on this idea.

Matthew


(John McCall) #2

Definitive initialization already ensures things like this; it should be straightforward to check the additional control-flow rules here if we can decide on how to write the check syntactically.

One constraint is that subobject initializers cannot allow “retries” after the delegating call, since user code can't distinguish semantically between failures that occurred before the instance was fully initialized (which can in principle be retried) and failures which occurred afterwards (which cannot, because the object may have escaped).

John.

···

On Dec 12, 2015, at 7:55 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
It is not currently possible to call a failable (optional) initializer from a throwing initializer. This is unfortunate as many Cocoa initializers are failable and it is desirable to be able to extend Cocoa types with throwing convenience initializers that call a failable (optional) designated initializer.

For example, something similar to the following should be possible:

enum CustomError: ErrorType { case E }

struct S {
   let urlString: String
}

extension NSURL {
   convenience init(s: S) throws {
       guard self.init(string: s.urlString)
           else { throw CustomError.E }
   }
}

In the current language, even if the compiler preventing us from calling the failable initializer we would not be able to access the return value in the guard expression. Furthermore, even if we could an Optional expression would not be valid in a guard condition. I am interested in ideas for how this should be handled. Would there be a special case of the guard statement to allow this? Or should some other mechanism be used to guarantee that we either successfully complete initialization or throw?


(Chris Lattner) #3

Right, the compiler would need to prove that the failure path from self.init returning nil could never lead to a normal return (it has to throw, abort, infinite loop, whatever).

Personally, I consider this an implementation limitation that doesn’t need an evolution proposal. Someone with a good patch to implement this would be welcome to submit a PR.

-Chris

···

On Dec 14, 2015, at 11:43 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 12, 2015, at 7:55 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
It is not currently possible to call a failable (optional) initializer from a throwing initializer. This is unfortunate as many Cocoa initializers are failable and it is desirable to be able to extend Cocoa types with throwing convenience initializers that call a failable (optional) designated initializer.

For example, something similar to the following should be possible:

enum CustomError: ErrorType { case E }

struct S {
  let urlString: String
}

extension NSURL {
  convenience init(s: S) throws {
      guard self.init(string: s.urlString)
          else { throw CustomError.E }
  }
}

In the current language, even if the compiler preventing us from calling the failable initializer we would not be able to access the return value in the guard expression. Furthermore, even if we could an Optional expression would not be valid in a guard condition. I am interested in ideas for how this should be handled. Would there be a special case of the guard statement to allow this? Or should some other mechanism be used to guarantee that we either successfully complete initialization or throw?

Definitive initialization already ensures things like this; it should be straightforward to check the additional control-flow rules here if we can decide on how to write the check syntactically.

One constraint is that subobject initializers cannot allow “retries” after the delegating call, since user code can't distinguish semantically between failures that occurred before the instance was fully initialized (which can in principle be retried) and failures which occurred afterwards (which cannot, because the object may have escaped).