Replace throws with Result


(JM) #1

There is currently a discussion open about throws, but it seems that the idea would be to add optionals to throws. In my opinion it would be much better to just get rid of all the throw error system.

I think an *Result<ErrorT, T>* type would provide much better error handling than the current solution:

- It would work just like Optional. We could use ?, ??, map, all those concepts that are already in use. A Result would basically be like a missing value with an error attached. *mapError* could also be added to transform error types to the appropriate value.

- We would have a specific error type. Right now throws just returns an *Error*, which is basically a glorified *Any*. Anything can go in there, and it's really hard (or impossible) to know if we are dealing with all the possible options. a Result type would enforce a specific error, so we would gain type safety and we could be sure we are dealing with all the cases.

- It would simplify everything. We could get rid of *try*, *try?*, *throws*, *rethrows*, *do … catch*.

- It could be used asynchronously by just passing the *Result* value. Right now there is a different mechanism for handling async and sync calls. The sync calls have all the *throws*, *rethrows*, must be taken care with *try* and *do … catch*, while in a callback you just send the error.


(Jaden Geller) #2

To be frank, there’s no way this is happening. The rationale for Swift’s error handling is documented <https://github.com/apple/swift/blob/master/docs/ErrorHandling.rst>, and this system was designed and implemented *after* optionals already existed in the language. I don’t think there’s any evidence that this was a horrible decision, especially one that requires a *majorly* source-breaking change to remove.

– Jaden

···

On May 3, 2017, at 3:06 AM, Jose Manuel Sánchez Peñarroja via swift-evolution <swift-evolution@swift.org> wrote:

There is currently a discussion open about throws, but it seems that the idea would be to add optionals to throws. In my opinion it would be much better to just get rid of all the throw error system.

I think an *Result<ErrorT, T>* type would provide much better error handling than the current solution:

- It would work just like Optional. We could use ?, ??, map, all those concepts that are already in use. A Result would basically be like a missing value with an error attached. *mapError* could also be added to transform error types to the appropriate value.

- We would have a specific error type. Right now throws just returns an *Error*, which is basically a glorified *Any*. Anything can go in there, and it's really hard (or impossible) to know if we are dealing with all the possible options. a Result type would enforce a specific error, so we would gain type safety and we could be sure we are dealing with all the cases.

- It would simplify everything. We could get rid of *try*, *try?*, *throws*, *rethrows*, *do … catch*.

- It could be used asynchronously by just passing the *Result* value. Right now there is a different mechanism for handling async and sync calls. The sync calls have all the *throws*, *rethrows*, must be taken care with *try* and *do … catch*, while in a callback you just send the error.

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


(Douglas Gregor) #3

To be frank, there’s no way this is happening. The rationale for Swift’s error handling is documented <https://github.com/apple/swift/blob/master/docs/ErrorHandling.rst>, and this system was designed and implemented *after* optionals already existed in the language. I don’t think there’s any evidence that this was a horrible decision, especially one that requires a *majorly* source-breaking change to remove.

As a core team member, let me amplify this response a bit: the current error-handling system will not be going away, for the reasons Jaden has stated above.

Maybe we will grow typed throws at some point; maybe Result will get added to the standard library with some nice affordances to map between throwing and Result-producing types; but we’re well beyond the point where we can rip out major features that have been generally well-received.

  - Doug

···

On May 3, 2017, at 3:31 AM, Jaden Geller via swift-evolution <swift-evolution@swift.org> wrote:

– Jaden

On May 3, 2017, at 3:06 AM, Jose Manuel Sánchez Peñarroja via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

There is currently a discussion open about throws, but it seems that the idea would be to add optionals to throws. In my opinion it would be much better to just get rid of all the throw error system.

I think an *Result<ErrorT, T>* type would provide much better error handling than the current solution:

- It would work just like Optional. We could use ?, ??, map, all those concepts that are already in use. A Result would basically be like a missing value with an error attached. *mapError* could also be added to transform error types to the appropriate value.

- We would have a specific error type. Right now throws just returns an *Error*, which is basically a glorified *Any*. Anything can go in there, and it's really hard (or impossible) to know if we are dealing with all the possible options. a Result type would enforce a specific error, so we would gain type safety and we could be sure we are dealing with all the cases.

- It would simplify everything. We could get rid of *try*, *try?*, *throws*, *rethrows*, *do … catch*.

- It could be used asynchronously by just passing the *Result* value. Right now there is a different mechanism for handling async and sync calls. The sync calls have all the *throws*, *rethrows*, must be taken care with *try* and *do … catch*, while in a callback you just send the error.

_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jean-Daniel) #4

There is currently a discussion open about throws, but it seems that the idea would be to add optionals to throws. In my opinion it would be much better to just get rid of all the throw error system.

I think an *Result<ErrorT, T>* type would provide much better error handling than the current solution:

At what cost ? Today, the error handling is barely zero-cost thanks to the swift calling convention. Having a generic return type will not only prevent calling convention optimization, but it will also add cost to all the return types as they will have to embed a discriminator.

- It would work just like Optional. We could use ?, ??, map, all those concepts that are already in use. A Result would basically be like a missing value with an error attached. *mapError* could also be added to transform error types to the appropriate value.

Is it really a benefit ? Working with functions that returns an optional (and can throw) will be far more complex as we will have to deal with 2 levels of unwrapping, one for the error, and a second one for the returned value.

- We would have a specific error type. Right now throws just returns an *Error*, which is basically a glorified *Any*. Anything can go in there, and it's really hard (or impossible) to know if we are dealing with all the possible options. a Result type would enforce a specific error, so we would gain type safety and we could be sure we are dealing with all the cases.

This issue can be resolved with typed throw. No need to break all existing code and rewrite the error handling for that.

- It would simplify everything. We could get rid of *try*, *try?*, *throws*, *rethrows*, *do … catch*.

All error handling pattern have a intrinsic complexity. I don’t see how having to call mapError after each call will make thing easier.

···

Le 3 mai 2017 à 12:06, Jose Manuel Sánchez Peñarroja via swift-evolution <swift-evolution@swift.org> a écrit :

- It could be used asynchronously by just passing the *Result* value. Right now there is a different mechanism for handling async and sync calls. The sync calls have all the *throws*, *rethrows*, must be taken care with *try* and *do … catch*, while in a callback you just send the error.


(JM) #5

At what cost ? Today, the error handling is barely zero-cost thanks to the swift calling convention. Having a generic return type will not only prevent calling convention optimization, but it will also add cost to all the return types as they will have to embed a discriminator.

I guess I’m thinking more in terms of usage and elegancy than performance. I don’t know which implications this might have.

Is it really a benefit ? Working with functions that returns an optional (and can throw) will be far more complex as we will have to deal with 2 levels of unwrapping, one for the error, and a second one for the returned value.

I think it would make very little sense to return a Result<ErrorT, Optional<T>>. Just like it doesn’t make much sense to return a Optional<Optional<T>>. It could still happen at some point, but the optional could be upgraded to Result and then everything flattened.

All error handling pattern have a intrinsic complexity. I don’t see how having to call mapError after each call will make thing easier.

In my opinion it’s easier to learn how optional works, and then use that knowledge for Result, instead of learning 2 different patterns for similar things. Optional can be used for computations where the error is obvious (like Array.first), and Result when more information is needed. Also, Result is not something very original. There are already some implementations of this for Swift, and it is widely used in Haskell (Either) and other languages for dealing with errors.

To be honest I never thought this would happen, as it involves a lot of breaking changes, but I expected to have a little discussion to see how feasible it would be in the long run.


(Jean-Daniel) #6

At what cost ? Today, the error handling is barely zero-cost thanks to the swift calling convention. Having a generic return type will not only prevent calling convention optimization, but it will also add cost to all the return types as they will have to embed a discriminator.

I guess I’m thinking more in terms of usage and elegancy than performance. I don’t know which implications this might have.

Is it really a benefit ? Working with functions that returns an optional (and can throw) will be far more complex as we will have to deal with 2 levels of unwrapping, one for the error, and a second one for the returned value.

I think it would make very little sense to return a Result<ErrorT, Optional<T>>. Just like it doesn’t make much sense to return a Optional<Optional<T>>. It could still happen at some point, but the optional could be upgraded to Result and then everything flattened.

Why that ? How I am supposed to fetch a nullable value from a database for instance and make a distinction between the value exists but is null and there where an error while accessing the data ?

How should I represent such API if Result<DataBaseError, String?> is not the way to go ?

All error handling pattern have a intrinsic complexity. I don’t see how having to call mapError after each call will make thing easier.

In my opinion it’s easier to learn how optional works, and then use that knowledge for Result, instead of learning 2 different patterns for similar things. Optional can be used for computations where the error is obvious (like Array.first), and Result when more information is needed. Also, Result is not something very original. There are already some implementations of this for Swift, and it is widely used in Haskell (Either) and other languages for dealing with errors.

Not a very convincing argument as Haskell is a niche language compare to the languages that use try/catch/throw like Java, C++, C#, …

···

Le 3 mai 2017 à 16:12, Jose Manuel Sánchez Peñarroja via swift-evolution <swift-evolution@swift.org> a écrit :

To be honest I never thought this would happen, as it involves a lot of breaking changes, but I expected to have a little discussion to see how feasible it would be in the long run.