[swift-evolution] Proposal: Allow Type Annotations on Throws

David Owens II david at owensd.io
Mon Dec 21 13:30:47 CST 2015


I’m just going to interject a few thoughts in here.

> On Dec 21, 2015, at 6:36 AM, Matthew Johnson <matthew at anandabits.com> wrote:
> 
> Hi David,
> 
> (my paraphrase: pattern matching is great and solves more of my concerns than I originally realized.) 

Awesome!

> I would also like to comment that there are some interesting related ideas for enhancing enums in the "[Pitch] Use enums as enum underlying types” thread.  They don’t directly impact the proposal but could make such use cases even more convenient if they are pursued independently.

I’m not sure I really like any of those proposals, but feedback for those is better on that thread, and I’ve only taken a cursory look at them.

> The other concern I have is still valid, but I think a relatively straightforward solution is possible.
> 
> (my paraphrase: Converting from an inner-error to a publicly exposed error is tedious, boilerplate code.)

Maybe. I think that depends on the assumption that you do nothing other than convert the inner error to a published error. Also, it assumes that all “recoverable” inner errors stay “recoverable” outer errors.

I tend to use a lot of telemetry markers and assertions within my code, so it’s not a one-liner conversion.

do {
    try some.implementation.detail.throwing.api()
}
catch {
    // what happens here?
}

I actually think there are various options of what to do at this place:

Simply propagate the error out (basically untyped errors)
Wrap the error (still exposes the internal implementation details)
Convert the error to a published error type
Promote the error to a non-recoverable error (e.g. fatalError())

All of these can have additional code in place, such as telemetry/logging or debug assertions. While it might be the case that people simply want to wrap an error, can we say that is the safe way to deal with the errors? I’m not sure I’m willing to make that statement.

I, obviously, recommend against doing options #1 and #2 above. I’ll talk about conversion below. And I believe promotion would look something like this:

guard ((try? some.implementation.detail.throwing.api()) != nil) else { fatalError("bad mojo!") }

Or if I want the error:

do {
    try some.implementation.detail.throwing.api()
}
catch {
    Telemetry.LogFatalIssue(error)
    fatalError("bad mojo!")
}

In an ideal world, I would also like to be able to do something like this:

guard try some.implementation.detail.throwing.api() else {
    Telemetry.LogFatalIssue(error)
    fatalError("bad mojo!")    
}

But that’s a syntax that’s out-of-scope for this proposal as well.

> This is the problem that `From` addresses in Rust.  Swift is not Rust and our solution will look different.  The point is that this is a problem and it can and has been solved.
> 
> My suggestion is that we should allow implicit conversion during error propagation.  If the published error type has one and only one non-failable, non-throwing initializer that takes a single argument of the type that is thrown (including enum case initializers with a single associated value of the thrown type) that initializer is used to implicitly convert to the published error type.  This conversion could be accomplished by synthesizing the necessary boilerplate or by some other means.
> 
> Now we have:
> 
> func functionThatCallsUnderlingyingThrows(_ whichError: Bool) throws MyPublishedError {
>         try funcThatThrowsAnErrorThatMustBeTranslatedIntoMyPublishedError()
> }
> 
> This looks as it should.  We don’t pay a price of boilerplate for carefully designing the errors we expose in our API contract.  This also handles automatic wrapping of errors where that is appropriate.

I would argue against implicit conversion. Again, this is about consistency with the language. Also, Rust is using it’s macro system to generate the code; it’s not actually doing implicit conversions either.

You could make it “nicer” by doing something like this:

try MyError.convertFrom(try funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError())

All of the “boiler-plate” code (which you need to write the conversion code regardless) can be put where it needs to be and kept out of all of the call sites. You could then propose a “conversion” feature to Swift that would allow explicit conversions:

try funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError() as MyError

This could call the conversion initializers.

-David

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151221/44586fce/attachment.html>


More information about the swift-evolution mailing list