[swift-evolution] Remove Failable Initializers

Haravikk swift-evolution at haravikk.me
Tue Mar 8 04:07:47 CST 2016


> On 8 Mar 2016, at 01:21, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> A duplicate shouldn’t be necessary; functions/initialisers that can throw just need to be called with a flag indicating whether they should capture or ignore errors as appropriate for try vs try? and try!. Any statement that is identified as being specific to a throw is then wrapped in a conditional based on this flag so it can be skipped if errors are ignored.
> 
> Okay, so you're passing in what amounts to a new parameter (which is going to take up a register or some stack space that could be used for something else) and adding a new conditional branch at each throw site. In cases where a `try` is nested directly inside a `throws` function, you might need a conditional branch at the return site, too. (Remember, CPUs hate conditional branches.)

It’s a conditional that should only have an impact when an error is being (or about to be) thrown, also I don’t think that a conditional at the call site would be necessary; if you know that try? or try! was used then you know that the flag was passed and that you’ll get no error instance back, just whatever usually passes the error status.

> I think that your use of the word "laziness" is telling. You are assuming that, if someone doesn't throw detailed errors, they are being lazy.

You’re assuming that every error has to be as detailed as possible ;)

I’m working from the assumption that part of updating the standard library to remove failable initialisers would include creating a set of common errors that developers can use too if they like. For example, yes, InvalidParameterError wouldn’t be super informative (though it at least informs you that the issue was with the parameter itself, and not something else that failed internally), however if there were errors for non-numeric input and such that this could provide extra information. You can certainly argue that it could still provide more information, but if we’re considering all failable initialisers to be “simple” then there will be a point at which you’re providing more information than anyone is actually going to use; a developer can certainly choose to do-so, but ultimately there may still be cases where just knowing that there was an error, or an error of a particular type, is enough.

> To illustrate, I spent ten or fifteen minutes examining IntegerParsing.swift.gyb so I could understand the failure cases of `Int.init(_:radix)`. To fully model all of the errors which can cause it to return `nil`, and without including any redundant information you could get from the string itself, you would probably need this enum:
> 
> 	enum IntFromStringError: ErrorType {
> 		case EmptyString
> 		case NoDigits
> 		case NegativeUnsigned
> 		case TooLarge (at: String.UTF16View.Index)
> 		case NonDigit (at: String.UTF16View.Index)
> 		case DigitBeyondRadix (at: String.UTF16View.Index)
> 	}
> 
> if your answer is "Don't do all that, just lump everything together into one vague error", then why are we using the throwing mechanism in the first place? Failable initializers convey one vague error just as well and with much less fuss.

My answer would be more along the lines of “just lump everything together into reasonably specific errors”. For example, EmptyString and NoDigits could easily be handled by a general purpose non-numeric error, while the rest could be handled by an integer out of range error of some kind; that should be plenty to communicate the problem, while detail messages could provide further information to the developer if the error is coming up unexpectedly (e.g- from input you thought was safe).

Instead of “something was wrong” you would then have two possibilities for what was wrong, and the potential to get more information if you need it during testing.

> If an error should only occur during debugging, it should be a precondition, not a failable *or* throwing initializer.
> 
> By definition, any throwable error should be something you expect to happen in the normal course of running the code out in the wild. That means there needs to be enough detail to either automatically fix the problem or usefully present it to a user, either textually or with some kind of graphical representation (like pointing to the invalid character).

Not quite what I meant; I would absolutely expect the error to occur in the wild, and be caught or ignored as appropriate, the message is just for cases where you’re not sure why it’s occurred in the first place, i.e- the error (and its type) is often enough all you need in simpler error cases, but if you’re getting them unexpectedly rather than due to anticipated mistakes in input etc. then you may want the extra detail. You certainly could model it for programmatic inspection and that’s an option too, but it may not be necessary.

Just because someone opts to use error handling over a failable initialiser doesn’t mean they have to go overboard on the detail of their errors; it’s entirely possible to pick a reasonable middle-ground. In other words, the work you put in should absolutely reflect some kind of value, but you seem to be assuming that the maximum possible amount of work has to be put into every error type thrown, where I think that most simple errors can be adequately modelled from a decent set of default error types; anything more complex absolutely should go into more detail as appropriate, but again they don’t necessarily have to expose the most minute of details.


More information about the swift-evolution mailing list