[Pitch] Typed throws


(Anton Zhilin) #1

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult

It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.


Why doesn't Swift have explicit throwables like Java
(David Sweeris) #2

That mostly works for me. I’m a bit hesitant because I’m not sure how well that syntax would scale (if there are multiple types of errors that could be thrown). If the consensus is that such a scenario isn’t likely to be a problem in practice, then +1

- Dave Sweeris

···

On Feb 17, 2017, at 10:45 AM, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult
It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.


(Kevin Nattinger) #3

protocol MyError: Error {}
enum MyFooError: MyError { … }
enum MyBarError: MyError { … }

func baz() throws(MyError)

···

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 19:47:47, Anton Zhilin via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult
It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.

_______________________________________________
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


(David Sweeris) #4

I think right now, there’s a bit of compiler magic in that you can only throw something that conforms to `Error`.

- Dave Sweeris

···

On Feb 17, 2017, at 11:12 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Is the throwing type always a protocol? In your example it is, but is this going to be always the case?


(Xiaodi Wu) #5

Sure thing, but that’s not what I was asking about. Kevin made a protocol
that conforms to Error where all the his enums conformed to MyError
protocol. That way we’re losing all enum cases and are not really a step
further as before.

Sorry, can you elaborate? What do you mean by losing enum cases?

Am 17. Februar 2017 um 20:18:48, David Sweeris (davesweeris@mac.com)

···

On Fri, Feb 17, 2017 at 1:22 PM, Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

schrieb:

On Feb 17, 2017, at 11:12 AM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

Is the throwing type always a protocol? In your example it is, but is this
going to be always the case?

I think right now, there’s a bit of compiler magic in that you can only
throw something that conforms to `Error`.

- Dave Sweeris

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


(Joe Groff) #6

Do you actually need that? Experience in other languages like Rust and Haskell that use Result-based error propagation suggests that a single error type is adequate, and beneficial in many ways. If nothing else, you could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a simpler model for this. We could say that the `throws` type is a generic parameter of all function types, and it defaults to the uninhabited `Never` type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic `throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could support throwing sequences:

protocol IteratorProtocol {
  associatedtype Element
  associatedtype Error: Swift.Error = Never

  mutating func next() throws Error -> Element?
}

Separate of the type system model, the type *checking* model also deserves thorough consideration. Propagating the effects of possibly multiple error types propagating within a `do` block is much trickier than doing so as a single "throws" or not bit, especially if you want to be able to use type context in `catch` patterns or to implicitly propagate a narrower `throws` type out of the enclosing function.

-Joe

···

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult


(Matthew Johnson) #7

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

Do you actually need that? Experience in other languages like Rust and Haskell that use Result-based error propagation suggests that a single error type is adequate, and beneficial in many ways. If nothing else, you could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a simpler model for this. We could say that the `throws` type is a generic parameter of all function types, and it defaults to the uninhabited `Never` type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic `throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could support throwing sequences:

protocol IteratorProtocol {
  associatedtype Element
  associatedtype Error: Swift.Error = Never

  mutating func next() throws Error -> Element?
}

Separate of the type system model, the type *checking* model also deserves thorough consideration. Propagating the effects of possibly multiple error types propagating within a `do` block is much trickier than doing so as a single "throws" or not bit, especially if you want to be able to use type context in `catch` patterns or to implicitly propagate a narrower `throws` type out of the enclosing function.

I agree with all of this. The correct way to handle functions that throw multiple error types is with a discriminated union (enum) or an existential.

If we make enums easier to work with (see my manifesto on generalized enums and value subtyping), we also make it easier to address the use case of functions that need to throw several different error types. That is the more general and useful way to address this use case.

···

On Feb 17, 2017, at 3:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Adrian Zubarev) #8

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 19:47:47, Anton Zhilin via swift-evolution (swift-evolution@swift.org) schrieb:

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult
It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.

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


(Adrian Zubarev) #9

Is the throwing type always a protocol? In your example it is, but is this going to be always the case?

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:08:38, Kevin Nattinger (swift@nattinger.net) schrieb:

protocol MyError: Error {}
enum MyFooError: MyError { … }
enum MyBarError: MyError { … }

func baz() throws(MyError)

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 19:47:47, Anton Zhilin via swift-evolution (swift-evolution@swift.org) schrieb:

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult
It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.

_______________________________________________
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


(Adrian Zubarev) #10

Sure thing, but that’s not what I was asking about. Kevin made a protocol that conforms to Error where all the his enums conformed to MyError protocol. That way we’re losing all enum cases and are not really a step further as before.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:18:48, David Sweeris (davesweeris@mac.com) schrieb:

On Feb 17, 2017, at 11:12 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Is the throwing type always a protocol? In your example it is, but is this going to be always the case?

I think right now, there’s a bit of compiler magic in that you can only throw something that conforms to `Error`.

- Dave Sweeris


(Adrian Zubarev) #11

The function that throws MyError might contain your error enum (or other types in general), but you would have to recover from MyType to the dynamic type first.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:26:48, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

On Fri, Feb 17, 2017 at 1:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
Sure thing, but that’s not what I was asking about. Kevin made a protocol that conforms to Error where all the his enums conformed to MyError protocol. That way we’re losing all enum cases and are not really a step further as before.

Sorry, can you elaborate? What do you mean by losing enum cases?

Am 17. Februar 2017 um 20:18:48, David Sweeris (davesweeris@mac.com) schrieb:

On Feb 17, 2017, at 11:12 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Is the throwing type always a protocol? In your example it is, but is this going to be always the case?

I think right now, there’s a bit of compiler magic in that you can only throw something that conforms to `Error`.

- Dave Sweeris

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


(Adrian Zubarev) #12

Sorry I meant “recover from MyError (protocol)”.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:30:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

The function that throws MyError might contain your error enum (or other types in general), but you would have to recover from MyType to the dynamic type first.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:26:48, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

On Fri, Feb 17, 2017 at 1:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
Sure thing, but that’s not what I was asking about. Kevin made a protocol that conforms to Error where all the his enums conformed to MyError protocol. That way we’re losing all enum cases and are not really a step further as before.

Sorry, can you elaborate? What do you mean by losing enum cases?

Am 17. Februar 2017 um 20:18:48, David Sweeris (davesweeris@mac.com) schrieb:

On Feb 17, 2017, at 11:12 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Is the throwing type always a protocol? In your example it is, but is this going to be always the case?

I think right now, there’s a bit of compiler magic in that you can only throw something that conforms to `Error`.

- Dave Sweeris

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


(Daniel Leping) #13

Here we go again... -1

···

On Fri, 17 Feb 2017 at 23:52 Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 3:45 PM, Joe Groff via swift-evolution < > swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error
types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

Do you actually need that? Experience in other languages like Rust and
Haskell that use Result-based error propagation suggests that a single
error type is adequate, and beneficial in many ways. If nothing else, you
could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a
simpler model for this. We could say that the `throws` type is a generic
parameter of all function types, and it defaults to the uninhabited `Never`
type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic
`throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could
support throwing sequences:

protocol IteratorProtocol {
  associatedtype Element
  associatedtype Error: Swift.Error = Never

  mutating func next() throws Error -> Element?
}

Separate of the type system model, the type *checking* model also deserves
thorough consideration. Propagating the effects of possibly multiple error
types propagating within a `do` block is much trickier than doing so as a
single "throws" or not bit, especially if you want to be able to use type
context in `catch` patterns or to implicitly propagate a narrower `throws`
type out of the enclosing function.

I agree with all of this. The correct way to handle functions that throw
multiple error types is with a discriminated union (enum) or an
existential.

If we make enums easier to work with (see my manifesto on generalized
enums and value subtyping), we also make it easier to address the use case
of functions that need to throw several different error types. That is the
more general and useful way to address this use case.

-Joe
_______________________________________________
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


(Anton Zhilin) #14

Is the throwing type always a protocol? In your example it is, but is this

going to be always the case?

I thought it was going to be any one subtype of Error, be it a struct, an
enum, or a protocol existential, or Error itself.

···

2017-02-17 22:12 GMT+03:00 Adrian Zubarev via swift-evolution < swift-evolution@swift.org>:


(Anton Zhilin) #15

In this case, you’d better create a new error type, which includes all the
cases of those errors:

// FileNotFoundError and WrongFormat are Error-s

struct PreferencesError : Error {
    init(_: FileNotFoundError)
    init(_: WrongFormat)
    // ...
}

func readPreferences() throws(PreferencesError)

In the most “lazy” case, you’d just create an enum of those two:

enum PreferencesError : Error {
    case fileNotFound(FileNotFoundError)
    case wrongFormat(WrongFormatError)
}

Better yet, you should analyze, which cases are meaningful for user of
readPreferences, and present them with appropriate interface. You may want
to crash on those cases of initial error types, with which you can’t do
anything on the level of abstraction of readPreferences. Some of the others
will be merged or renamed.

With proper error types, a single type in throws clause is enough, without
sum types.

Sure thing, but that’s not what I was asking about. Kevin made a protocol

that conforms to Error where all the his enums conformed to MyError
protocol. That way we’re losing all enum cases and are not really a step
further as before.

···

2017-02-17 22:22 GMT+03:00 Adrian Zubarev via swift-evolution < swift-evolution@swift.org>:


(Adrian Zubarev) #16

No actually not, it was made up by the assumption that the proposed syntax would have more than one throwing that which was clarified by others to be incorrect. :wink:

Thanks again for clarification.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 22:45:35, Joe Groff (jgroff@apple.com) schrieb:

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult
Do you actually need that? Experience in other languages like Rust and Haskell that use Result-based error propagation suggests that a single error type is adequate, and beneficial in many ways. If nothing else, you could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a simpler model for this. We could say that the `throws` type is a generic parameter of all function types, and it defaults to the uninhabited `Never` type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic `throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could support throwing sequences:

protocol IteratorProtocol {
associatedtype Element
associatedtype Error: Swift.Error = Never

mutating func next() throws Error -> Element?
}

Separate of the type system model, the type *checking* model also deserves thorough consideration. Propagating the effects of possibly multiple error types propagating within a `do` block is much trickier than doing so as a single "throws" or not bit, especially if you want to be able to use type context in `catch` patterns or to implicitly propagate a narrower `throws` type out of the enclosing function.

-Joe


(Colin Barrett) #17

I really dig this proposal, Joe. Matches my experience from other languages
as well.

-Colin

···

On Fri, Feb 17, 2017 at 4:46 PM Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error
types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

Do you actually need that? Experience in other languages like Rust and
Haskell that use Result-based error propagation suggests that a single
error type is adequate, and beneficial in many ways. If nothing else, you
could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a
simpler model for this. We could say that the `throws` type is a generic
parameter of all function types, and it defaults to the uninhabited `Never`
type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic
`throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could
support throwing sequences:

protocol IteratorProtocol {
  associatedtype Element
  associatedtype Error: Swift.Error = Never

  mutating func next() throws Error -> Element?
}


(Karl) #18

So, I’m not sure about what was decided last time, but my issues with this are:

- The thrown error type will become part of the ABI of the function. If you change the type of Error that is thrown, callers may not catch it. At the same time, if we make enums resilient by default and only allow specifying a single entire type, you will basically need one Error enum per function and it will need to be @fixed if you actually want to remove the catch-all block. Otherwise:

// Let’s say this isn’t @fixed...
enum CanFailError {
    errorOne
    errorTwo
}

func canFail() throws(CanFailError) { /* … */ }

do { try canFail() }
catch CanFailError {
    switch error {
        case .errorOne: /* handle error one */
        case .errorTwo: /* handle error two */
        default: /* handle possible new errors in later versions of the library */
    }
}

do { try canFail() }
catch .errorOne { /* handle error one */ }
catch .errorTwo { /* handle error two */ }
catch { /* handle possible new errors in later versions of the library */ }

- I usually have _semantic_ namespaces for Errors, rather than single types per implementation pattern. If we are adding strong annotations about which errors can be thrown, I’d quite like to incorporate that pattern. For example:

extension File {
@fixed enum OpeningError {
  case .invalidPath
  case .accessDenied // e.g. asking for write permissions for read-only file
}
@fixed enum ReadError {
  case .invalidOffset // past EOF
  case .deviceError // probably worth aborting the entire operation the read is part of
}

// - throws:
// - .OpeningError if the file can’t be opened
// - .ReadError if the read operation fails
func read(from offset: Int, into buffer: UnsafeBufferPointer<UInt8>) throws(OpeningError, ReadError) { /* … */ }
}

- I wonder if we could try something more ambitious. Since the list of thrown errors is resilience-breaking for the function, it is only beneficial for versioned and @inlineable functions. They should not be able to add new errors (they can remove them though, since errors are intended to be switched over). I wonder if we couldn’t introduce a small pattern grammar for our structured comments (isolated from the rest of the language) - it would be optional, but if you do list your errors, the compiler would validate that you do it exhaustively. Some patterns I would like are:

// - throws: - MyError.{errorOne, errorThree, errorFive}: Something bad || considered exhaustive
@inlineable public func canFail() throws {}

// - throws: - OpeningError: Computer says nooooo... || considered exhaustive if OpeningError is versioned or @fixed
// - * || other errors, requires “catch-all” by external callers
@inlineable public func canFail2() throws {}

If we want to get really clever, we can have the compiler automatically generate those error-lists for internal functions, so you would automatically get exhaustive error-handling within your own module.

- Karl

···

On 17 Feb 2017, at 19:45, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

Now this is on-topic, I guess.
Last time we stopped at John McCall’s syntax:

extension MyError: Error { ... }

func foo() throws(MyError) -> MyResult
It’s conservative and prevents visual ambiguity with extra parentheses.

If we (somewhat) agree on this, then submitting a proposal will be trivial.

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


(Brandon Knope) #19

I really like this. Seems much more elegant and simple this way

···

On Feb 17, 2017, at 4:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 11:03 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I suggest we need to find a way to shorten the list of the possible error types with a the help of typeallias

extension MyError1: Error { ... }
extension MyError2: Error { ... }
extension MyError3: Error { ... }

typealias MyErrors = MyError1 | MyError2 | MyError3

func foo() throws(MyErrors) -> MyResult
func bar<T : Error>(_: () throws(T) -> Void) rethrows(MyErrors, T) -> MyResult

Do you actually need that? Experience in other languages like Rust and Haskell that use Result-based error propagation suggests that a single error type is adequate, and beneficial in many ways. If nothing else, you could `Either` your way to multiple errors if you really needed to.

IMO, if we accept a single error type per function, there could be a simpler model for this. We could say that the `throws` type is a generic parameter of all function types, and it defaults to the uninhabited `Never` type for functions that don't throw.

() -> () == () throws Never -> ()
() throws -> () == () throws Error -> ()

In this model, you'd get many benefits:

- `rethrows` could become first-class, reducing down to just polymorphic `throws`:

func foo(_: () throws -> ()) rethrows // Swift 3
func foo<T: Error>(_: () throws T -> ()) throws T // Swift X
func foo<T: Error>(_: () throws T -> ()) throws Either<MyErrors, T>

- Protocols could abstract over error handling; for instance, we could support throwing sequences:

protocol IteratorProtocol {
  associatedtype Element
  associatedtype Error: Swift.Error = Never

  mutating func next() throws Error -> Element?
}

Separate of the type system model, the type *checking* model also deserves thorough consideration. Propagating the effects of possibly multiple error types propagating within a `do` block is much trickier than doing so as a single "throws" or not bit, especially if you want to be able to use type context in `catch` patterns or to implicitly propagate a narrower `throws` type out of the enclosing function.

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


(Dave Abrahams) #20

And experience in still others, like C++ and Java, suggests that
using static types to restrict the kind of information a function can
give you when an error occurs may actually be harmful.

···

on Fri Feb 17 2017, Joe Groff <swift-evolution@swift.org> wrote:

Experience in other languages like Rust and Haskell that use
Result-based error propagation suggests that a single error type is
adequate, and beneficial in many ways.

--
-Dave