Proposal: Allow Type Annotations on Throws

Thanks for the clarification. Why is the compiler saying that the catch is not exhaustive when it is covering all the possible values of the enum? Is it to be able to catch future values added to the enum type?

···

On Dec 18, 2015, at 8:05 PM, David Owens II <david@owensd.io> wrote:

On Dec 18, 2015, at 4:38 PM, Ricardo Parada <rparada@mac.com <mailto:rparada@mac.com>> wrote:

Hi David

I started reading your proposal and I have a couple of questions.

In the Enum Base ErrorType example you mentioned that it requires a "catch { }" clause. However the code is already covering the two possible Enum values (OffBy1 and MutatedValue). Why is the "catch { }" required? I typed that code into a playground and I did not get any errors. Are you saying that because the Enum type could add a value in the future?

Playgrounds are basically in an anonymous function that throws, so the problem doesn’t show up there at the top level. Copy this into your playground.

enum MyError: ErrorType {
    case OnlyOne
}

func thrower() throws { throw MyError.OnlyOne }

func nolies() {
    do {
        try thrower()
    }
    catch MyError.OnlyOne { print("handled") }
    // catch { print("compiler error until uncommented") }
}

Also, you proposed the catch clause to use error as the name of the constant holding the error. Wouldn't it be better to let the programmer decide the name rather than hard coding it to use error? For example:

catch e where e.value == 0 { print("0") }
catch e where e.value == 1 { print("1") }
catch { print("nothing") }

The “error” name is already specified in the Swift rules for what the constant is. I don’t see any compelling reason to propose a change to that.

-David

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

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

···

On Dec 21, 2015, at 6:36 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Right now, it's because throws does not specify a type to be thrown, and the compiler doesn't know that you've checked all the possibilities. For all it knows, it could be something else than a MyError.

···

Le 18 déc. 2015 à 21:28:41, Ricardo Parada via swift-evolution <swift-evolution@swift.org> a écrit :

Thanks for the clarification. Why is the compiler saying that the catch is not exhaustive when it is covering all the possible values of the enum? Is it to be able to catch future values added to the enum type?

On Dec 18, 2015, at 8:05 PM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

On Dec 18, 2015, at 4:38 PM, Ricardo Parada <rparada@mac.com <mailto:rparada@mac.com>> wrote:

Hi David

I started reading your proposal and I have a couple of questions.

In the Enum Base ErrorType example you mentioned that it requires a "catch { }" clause. However the code is already covering the two possible Enum values (OffBy1 and MutatedValue). Why is the "catch { }" required? I typed that code into a playground and I did not get any errors. Are you saying that because the Enum type could add a value in the future?

Playgrounds are basically in an anonymous function that throws, so the problem doesn’t show up there at the top level. Copy this into your playground.

enum MyError: ErrorType {
    case OnlyOne
}

func thrower() throws { throw MyError.OnlyOne }

func nolies() {
    do {
        try thrower()
    }
    catch MyError.OnlyOne { print("handled") }
    // catch { print("compiler error until uncommented") }
}

Also, you proposed the catch clause to use error as the name of the constant holding the error. Wouldn't it be better to let the programmer decide the name rather than hard coding it to use error? For example:

catch e where e.value == 0 { print("0") }
catch e where e.value == 1 { print("1") }
catch { print("nothing") }

The “error” name is already specified in the Swift rules for what the constant is. I don’t see any compelling reason to propose a change to that.

-David

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Thanks for continuing the discussion David.

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

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.

Sure, just wanted to mention them. I’m not sure whether I like any of them either - even the ones I have posted. I do think they are interesting to think about but I am not sure whether they are good ideas or not.

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 agree. I am proposing a solution to reduce boilerplate where either option 2 or 3 is the right thing to do. Obviously if you need to do something more complex you do need to handle the error, in which case it is not boilerplate. The suggestion of implicit conversion would not prevent you from doing this.

Please note: sometimes option 2 exposes implementation details but not necessarily. The example I discussed was describing a scenario where a library has a family of possible errors it publishes as part of its API contract and two (or more) different ones may be generated by the same function.

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.

I understand that Rust is not doing implicit conversions, but the effect for the user is pretty much the same. The try macro is converting the underlying error to the type that can be propagated. As I stated, Swift is not Rust and deserves a different solution.

Nevertheless, that does not minimize the need to solve the problem. I maintain that the problem solved by the try macro is a significant one that is not addressed by the current proposal. I would really like to see it addressed one way or another.

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

try MyError.convertFrom(try funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError())

Can you elaborate on how you think this would work? If funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError actually throws it will be propagated to the next enclosing catch clause. MyError.convertFrom will not have a chance to do anything with it.

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.

This would be casting the return value, not the error value. Again, the error would be propagated to the next enclosing catch block.

I would probably be ok with the need to *explicitly* declare a conversion like this at the call site if that were possible (although I would want to consider concrete examples). It just isn’t possible in the language today so 1) it’s hard to know exactly what it would look like and 2) it shouldn’t be part of the discussion unless we are considering adding something specific to the proposal.

Are you willing to explore adding *explicit* syntax to convert thrown errors to your proposal? That seems like it might be a reasonable compromise between implicit conversions and manual boilerplate.

I still prefer implicit conversions in this case and believe the value they bring to the table far outweighs their cost. We are not talking about implicit conversion of arbitrary expressions here. We are talking about implicit conversion specifically at the time of error propagation *from* the explicitly stated error type in throwing functions that are called *to* the explicitly stated error type in the calling function *if* an eligible initializer exists.

This is a pretty narrow case with pretty obvious results IMO. And it only kicks in if the underlying error is propagated out of the function so you still have the ability to catch it and log it, suppress it, convert to fatalError(), etc if you need to do that.

Matthew

···

On Dec 21, 2015, at 1:30 PM, David Owens II <david@owensd.io> wrote:

On Dec 21, 2015, at 6:36 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

I understand that Rust is not doing implicit conversions, but the effect for the user is pretty much the same. The try macro is converting the underlying error to the type that can be propagated. As I stated, Swift is not Rust and deserves a different solution.

Nevertheless, that does not minimize the need to solve the problem. I maintain that the problem solved by the try macro is a significant one that is not addressed by the current proposal. I would really like to see it addressed one way or another.

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

try MyError.convertFrom(try funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError())

Can you elaborate on how you think this would work? If funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError actually throws it will be propagated to the next enclosing catch clause. MyError.convertFrom will not have a chance to do anything with it.

Here’s a full playground example (I’ve annotated in comments where the type of error could be described):

enum InternalError: ErrorType {
    case Internal(value: Int)
}

enum PublishedError: ErrorType {
    static func from<T>(@autoclosure fn: () throws -> T) throws -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
        catch {
            fatalError("unsupported conversion")
        }
    }
    
    case Converted(value: Int)
}

func example() {

    func bad(value: Int) throws /* InternalError */ -> Int {
        if value % 2 == 0 { throw InternalError.Internal(value: value) }
        return value
    }

    func verbose(value: Int) throws /* PublishedError */ -> Int {
        do {
            return try bad(value)
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
        catch {
            fatalError("unsupported conversion")
        }
    }
    
    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try PublishedError.from(try bad(value))
    }
    
    do {
        let r1 = try verbose(11)
        print("verbose: \(r1)")
        
        let r2 = try convert(9)
        print("converted: \(r2)")
    }
    catch {
        print("error: \(error)")
    }

}

example()

As you can see, the “verbose()” and the “from()” conversion are basically the same implementation. What I’m saying is that I believe you can simply do the explicit conversion yourself without much fanfare (compare the verbose() and convert() implementations).

In the implementation of PublishedError.from() you can use Swift’s pattern matching to do all of your conversions in a single place. Note that where the implementation of “from” is at doesn’t matter, it could be on another type or a free function, whatever.

Are you willing to explore adding *explicit* syntax to convert thrown errors to your proposal? That seems like it might be a reasonable compromise between implicit conversions and manual boilerplate.

The only boiler plate I’m seeing is the explicit conversion call: PublishedError.from(try bad(value))

Am I misunderstanding something?

To me, this would be much more confusing:

    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try bad(value) /* implicit conversion from InternalError -> PublishedError */
    }

If there were implicit type conversions, this would have to be something that Swift supported all up. I’d be very hesitant to make this work for only errors. For example, how does implicit conversion work if we can later extend this to async behaviors? Do we have special conversions that can take an async error make it a synchronous error? How about vice-versa?

I guess I wouldn’t want to go further than having explicit conversions until we better understood all of those answers and how implicit type conversion would work in Swift generally. If I recall, Swift had implicit type conversion in the early versions, and it has been removed in most places.

-David

I understand that Rust is not doing implicit conversions, but the effect for the user is pretty much the same. The try macro is converting the underlying error to the type that can be propagated. As I stated, Swift is not Rust and deserves a different solution.

Nevertheless, that does not minimize the need to solve the problem. I maintain that the problem solved by the try macro is a significant one that is not addressed by the current proposal. I would really like to see it addressed one way or another.

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

try MyError.convertFrom(try funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError())

Can you elaborate on how you think this would work? If funcThatThrowsAnErrorThatMustBeTranslatedItoMyPublishedError actually throws it will be propagated to the next enclosing catch clause. MyError.convertFrom will not have a chance to do anything with it.

Here’s a full playground example (I’ve annotated in comments where the type of error could be described):

enum InternalError: ErrorType {
    case Internal(value: Int)
}

enum PublishedError: ErrorType {
    static func from<T>(@autoclosure fn: () throws -> T) throws -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
        catch {
            fatalError("unsupported conversion")
        }
    }
    
    case Converted(value: Int)
}

func example() {

    func bad(value: Int) throws /* InternalError */ -> Int {
        if value % 2 == 0 { throw InternalError.Internal(value: value) }
        return value
    }

    func verbose(value: Int) throws /* PublishedError */ -> Int {
        do {
            return try bad(value)
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
        catch {
            fatalError("unsupported conversion")
        }
    }
    
    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try PublishedError.from(try bad(value))
    }
    
    do {
        let r1 = try verbose(11)
        print("verbose: \(r1)")
        
        let r2 = try convert(9)
        print("converted: \(r2)")
    }
    catch {
        print("error: \(error)")
    }

}

example()

As you can see, the “verbose()” and the “from()” conversion are basically the same implementation. What I’m saying is that I believe you can simply do the explicit conversion yourself without much fanfare (compare the verbose() and convert() implementations).

In the implementation of PublishedError.from() you can use Swift’s pattern matching to do all of your conversions in a single place. Note that where the implementation of “from” is at doesn’t matter, it could be on another type or a free function, whatever.

That is a pretty clever use of @autoclosure! It can be made even be made even more concise with typed errors and a top level conversion function:

@protocol ErrorTypeConvertible {
  // implementations will have to include a default clause which is either going to call fatalError
        // or be an ‘UnknownError’ case in the enum
  init(underlyingError: ErrorType) { … }
        // or
  init<E: ErrorType>(underlyingError: E) { … }
}

func from<T/*, Internal, Published: ErrorTypeConvertible*/>(@autoclosure fn: () throws /* Internal */ -> T) throws /* Published */ -> T {
    do {
        return try fn()
    }
    catch let error as Internal {
        return Published(underlyingError: error)
    }
   // hopefully the compiler is able to detect that this is
}

    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try from(try bad(value))
    }

This addresses my largest concern which is cluttering up the control flow. The additional noise of an extra ‘try' and a call to ‘from’ isn’t great, but it is tolerable, at least initially (I think we would eventually learn that it is just noise and get rid of it).

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

If you can make PublishedError.from safe without requiring an ‘UnknownError’ case it will also be possible to make a top-level ‘from’ safe. That would be acceptable, but I don’t believe it’s possible in the current language and I’m not aware of any proposed changes that would make it possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence. The obvious thing to do is allow them to behave the same as any other part of the return type. If that is what you expect and it is not already stated, you should update the proposal to specify that. If you expect something different you definitely need to specify what that is. An implementer will need to know how this should be handled.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

Matthew

···

On Dec 21, 2015, at 3:00 PM, David Owens II <david@owensd.io> wrote:

Are you willing to explore adding *explicit* syntax to convert thrown errors to your proposal? That seems like it might be a reasonable compromise between implicit conversions and manual boilerplate.

The only boiler plate I’m seeing is the explicit conversion call: PublishedError.from(try bad(value))

Am I misunderstanding something?

To me, this would be much more confusing:

    func convert(value: Int) throws /* PublishedError */ -> Int {
        return try bad(value) /* implicit conversion from InternalError -> PublishedError */
    }

If there were implicit type conversions, this would have to be something that Swift supported all up. I’d be very hesitant to make this work for only errors. For example, how does implicit conversion work if we can later extend this to async behaviors? Do we have special conversions that can take an async error make it a synchronous error? How about vice-versa?

I guess I wouldn’t want to go further than having explicit conversions until we better understood all of those answers and how implicit type conversion would work in Swift generally. If I recall, Swift had implicit type conversion in the early versions, and it has been removed in most places.

-David

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

-David

···

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

···

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

···

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I agree, David's proposal looks good.

Matthew concerned are interesting, and I think David answered them well.

Pierre

···

Le 23 déc. 2015 à 06:58, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> a écrit :

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi!
I've been following the conversation closely so here are my 2c

TL;TR: +1

After proposing solutions for the concerns about conversions this is a clear +1. Overloading the conversion functions is how it works in Rust and I think is a good tradeoff, even without the magical "try!" That Rust has. Maybe we could explore something like that when Swift give us a macro like system. But for now I will be happy with this.

The most important thing to remember is that this changes will not affect people that prefers the untyped throws, as they can continue doing it like now. I think this is really important as will allow us to ignore typed errors when writing prototyping code, but it will give the option to improve the safety of our code when is time for that.

Thanks for the proposal and the interesting discussion.

PS: i've been playing on porting the example from Rust errors page to Swift.

Will be interesting to try it again with the changes from this proposal ;)

···

Sent from my iPad

On 23 Dec 2015, at 05:58, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi!
I've been following the conversation closely so here are my 2c

TL;TR: +1

After proposing solutions for the concerns about conversions this is a clear +1. Overloading the conversion functions is how it works in Rust and I think is a good tradeoff, even without the magical "try!" That Rust has. Maybe we could explore something like that when Swift give us a macro like system. But for now I will be happy with this.

Rust actually uses a trait (the Rust equivalent of a protocol) to do this. It isn't possible in Swift because protocols cannot have type parameters like traits can in Rust and there is no other mechanism in Swift with equivalent capability.

This means that Rust's From mechanism is generic and is part of the standard library. It is part of the common vocabulary of Rust programmers. This is a significant advantage over a solution that uses ad-hoc overloading relying on a convention that is likely to vary between teams. Some teams probably won't even be aware of the possibility to adopt such a convention if it isn't part of our common vocabulary.

Here is what the Rust documentation has to say about this:

From is very useful because it gives us a generic way to talk about conversion from a particular type T to some other type (in this case, “some other type” is the subject of the impl, or Self). The crux of From is the set of implementations provided by the standard library.

Swift is not Rust and I personally believe a different approach to handling error translation during propagation would be more Swifty for a number of reasons. I believe that would be the case even if we had macros and protocols with type parameters. I am continuing to think about this and will probably submit a proposal if / when David's proposal is accepted.

IMO it is very important that we have a standard mechanism for translating errors during propagation.

···

Sent from my iPad

On Dec 24, 2015, at 3:24 AM, Alejandro Martinez <alexito4@gmail.com> wrote:

The most important thing to remember is that this changes will not affect people that prefers the untyped throws, as they can continue doing it like now. I think this is really important as will allow us to ignore typed errors when writing prototyping code, but it will give the option to improve the safety of our code when is time for that.

Thanks for the proposal and the interesting discussion.

Sent from my iPad

PS: i've been playing on porting the example from Rust errors page to Swift.
Swift Typed Throws compared with Rust | Alexito's World
Will be interesting to try it again with the changes from this proposal ;)

On 23 Dec 2015, at 05:58, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Absolutely agree Matthew.
My point was that if this is the temporary tradeoff that we have to support for me is worth it. Having typed throws looks like a great next step to improving the current error system.
Curious to see your ideas about that ;)

Cheers,

···

Sent from my iPad

On 24 Dec 2015, at 14:26, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Dec 24, 2015, at 3:24 AM, Alejandro Martinez <alexito4@gmail.com> wrote:

Hi!
I've been following the conversation closely so here are my 2c

TL;TR: +1

After proposing solutions for the concerns about conversions this is a clear +1. Overloading the conversion functions is how it works in Rust and I think is a good tradeoff, even without the magical "try!" That Rust has. Maybe we could explore something like that when Swift give us a macro like system. But for now I will be happy with this.

Rust actually uses a trait (the Rust equivalent of a protocol) to do this. It isn't possible in Swift because protocols cannot have type parameters like traits can in Rust and there is no other mechanism in Swift with equivalent capability.

This means that Rust's From mechanism is generic and is part of the standard library. It is part of the common vocabulary of Rust programmers. This is a significant advantage over a solution that uses ad-hoc overloading relying on a convention that is likely to vary between teams. Some teams probably won't even be aware of the possibility to adopt such a convention if it isn't part of our common vocabulary.

Here is what the Rust documentation has to say about this:

From is very useful because it gives us a generic way to talk about conversion from a particular type T to some other type (in this case, “some other type” is the subject of the impl, or Self). The crux of From is the set of implementations provided by the standard library.

Swift is not Rust and I personally believe a different approach to handling error translation during propagation would be more Swifty for a number of reasons. I believe that would be the case even if we had macros and protocols with type parameters. I am continuing to think about this and will probably submit a proposal if / when David's proposal is accepted.

IMO it is very important that we have a standard mechanism for translating errors during propagation.

The most important thing to remember is that this changes will not affect people that prefers the untyped throws, as they can continue doing it like now. I think this is really important as will allow us to ignore typed errors when writing prototyping code, but it will give the option to improve the safety of our code when is time for that.

Thanks for the proposal and the interesting discussion.

Sent from my iPad

PS: i've been playing on porting the example from Rust errors page to Swift.
Swift Typed Throws compared with Rust | Alexito's World
Will be interesting to try it again with the changes from this proposal ;)

On 23 Dec 2015, at 05:58, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Absolutely agree Matthew.
My point was that if this is the temporary tradeoff that we have to support for me is worth it. Having typed throws looks like a great next step to improving the current error system.
Curious to see your ideas about that ;)

Agree with you on that. I think a proposal for proper error conversion during propagation could actually follow this one pretty quickly so we may not have to wait too long. I am hoping it is possible both could make it in the Swift 3 timeframe if both are accepted.

···

On Dec 24, 2015, at 7:51 PM, Alejandro Martinez <alexito4@gmail.com> wrote:

Cheers,

Sent from my iPad

On 24 Dec 2015, at 14:26, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Dec 24, 2015, at 3:24 AM, Alejandro Martinez <alexito4@gmail.com <mailto:alexito4@gmail.com>> wrote:

Hi!
I've been following the conversation closely so here are my 2c

TL;TR: +1

After proposing solutions for the concerns about conversions this is a clear +1. Overloading the conversion functions is how it works in Rust and I think is a good tradeoff, even without the magical "try!" That Rust has. Maybe we could explore something like that when Swift give us a macro like system. But for now I will be happy with this.

Rust actually uses a trait (the Rust equivalent of a protocol) to do this. It isn't possible in Swift because protocols cannot have type parameters like traits can in Rust and there is no other mechanism in Swift with equivalent capability.

This means that Rust's From mechanism is generic and is part of the standard library. It is part of the common vocabulary of Rust programmers. This is a significant advantage over a solution that uses ad-hoc overloading relying on a convention that is likely to vary between teams. Some teams probably won't even be aware of the possibility to adopt such a convention if it isn't part of our common vocabulary.

Here is what the Rust documentation has to say about this:

From is very useful because it gives us a generic way to talk about conversion from a particular type T to some other type (in this case, “some other type” is the subject of the impl, or Self). The crux of From is the set of implementations provided by the standard library <https://doc.rust-lang.org/std/convert/trait.From.html&gt;\.

Swift is not Rust and I personally believe a different approach to handling error translation during propagation would be more Swifty for a number of reasons. I believe that would be the case even if we had macros and protocols with type parameters. I am continuing to think about this and will probably submit a proposal if / when David's proposal is accepted.

IMO it is very important that we have a standard mechanism for translating errors during propagation.

The most important thing to remember is that this changes will not affect people that prefers the untyped throws, as they can continue doing it like now. I think this is really important as will allow us to ignore typed errors when writing prototyping code, but it will give the option to improve the safety of our code when is time for that.

Thanks for the proposal and the interesting discussion.

Sent from my iPad

PS: i've been playing on porting the example from Rust errors page to Swift.
Swift Typed Throws compared with Rust | Alexito's World
Will be interesting to try it again with the changes from this proposal ;)

On 23 Dec 2015, at 05:58, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

David's proposal looks good enough for me.

With regards to Matthew's worry of cluttering the code with conversion I'd like to remark that this conversion code should only live on the border of your API (the facade), so it should probably not be too invasive to your business logic.

-Thorsten

Am 22.12.2015 um 20:08 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Dec 22, 2015, at 12:09 PM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

On Dec 22, 2015, at 9:50 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Unfortunately, I don’t see a way to make it safe. You had to use fatalError in a default case to make it work. An alternative would have been to include an ‘UnknownError’ case in ‘PublishedError’. Neither is not an acceptable solution IMO.

I need the fatalError() because the sample is a working example in Swift today. If we had typed errors, this would simply work:

   static func from<T>(@autoclosure fn: () throws InternalError -> T) throws PublishedError -> T {
        do {
            return try fn()
        }
        catch InternalError.Internal(let value) {
            throw PublishedError.Converted(value: value)
        }
    }

This states that the only closure accepted is one that throws an InternalError.

Ok, so you suggest writing a specific overload for each combination of error types that are convertible. Got it. Not sure why I didn’t think of overloads. I was too focused on a general try function dispatching to an initializer.

That is indeed safe and I can live with it. Thanks for taking the time to work through these examples with me and help to identify patterns that address my concerns!

I still find it unfortunate that this is in the realm of a pattern though. IMO it would be much better if it was part of the common Swift vocabulary, either as a language feature or a library function but that isn’t possible as a generic implementation isn’t possible.

This top level `from` example also brings up a couple of points that I don’t recall being addressed in your proposal. Specifically, the interaction of typed errors with generics and type inferrence.

I call out in the proposal that errors work with generics no differently than other types.

Great, I must have missed that.

I still consider this to be an unresolved concern. I would like to have a safe way to perform error conversion during propagation without cluttering up my control flow and seriously degrading readability. This is a problem that can and has been solved in other languages. IMO it is should be considered an essential element of a proposal introducing typed errors.

When Swift has a macro as powerful as Rust, then this is a solved problem as well. However, Swift isn’t there yet.

I would prefer a solution to this that didn’t require macros which would fit better in Swift. This feature is buried in the `try!` macro in Rust as Rust doesn’t have built-in language level error handling support.

Swift already has `try` built into the language. IMO it would be better to have it handled by the built-in language level error handling support in Swift. That seems like the more “Swifty” approach. We could have a Swift macro `tryAndConvert` or something, but that seems inelegant.

We’ve gone back and forth on this quite a bit but nobody else has chimed in. I’m curious to hear what others thing. I would love it if any lurkers would jump in and comment!

Matthew

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution