[Pitch] Typed throws

Sorry, I couldn’t follow every thread. I simply couldn’t get that fact from the given context of the first post by Anton. :) Just forget everything I mentioned about typealias, because it was based on the assumption of an error list.

Anyways +1 for typed throws. The syntax throws(T) and rethrows(T) is fine by me.

I feel like rethrows can and should be inferred. It’s just a pass-through.

···

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

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:45:55, Matthew Johnson (matthew@anandabits.com <mailto:matthew@anandabits.com>) schrieb:

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

So the typed throws are going to be limited to a single error type, is that the direction we're heading to?

Yes, this topic was discussed thoroughly last year.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:39:12, Anton Zhilin (antonyzhilin@gmail.com <mailto:antonyzhilin@gmail.com>) schrieb:

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.

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

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.

_______________________________________________
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

Let's not bring bikeshedding the commonly proposed and rejected union
spelling into this.
Typed throws would be a nice addition, assuming that the core team finds it
in scope for phase 2. It seems only logical that any type can be thrown
(i.e. conforms to Error) should be permitted to be listed in `throws()`.

···

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

That’s what I thought too. That’s also the case I showed a simple example
how we could solve the giant list with a typealias even if the pipe |
operator would be exclusive for anything that conforms to Error.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:16:30, Anton Zhilin (antonyzhilin@gmail.com)
schrieb:

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

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.

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

Let's not bring bikeshedding the commonly proposed and rejected union spelling into this.
Typed throws would be a nice addition, assuming that the core team finds it in scope for phase 2. It seems only logical that any type can be thrown (i.e. conforms to Error) should be permitted to be listed in `throws()`.

Agree. Typed throws should have a single thrown type. Making it more convenient to throw and catch more than one error type with typed throws without having to manually create a wrapper is an orthogonal issue. David Owens convinced me of this last year when we had a thread on the topic.

···

On Feb 17, 2017, at 1:24 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Fri, Feb 17, 2017 at 1:18 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
That’s what I thought too. That’s also the case I showed a simple example how we could solve the giant list with a typealias even if the pipe | operator would be exclusive for anything that conforms to Error.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:16:30, Anton Zhilin (antonyzhilin@gmail.com <mailto:antonyzhilin@gmail.com>) schrieb:

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

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.

_______________________________________________
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

So the typed throws are going to be limited to a single error type, is that the direction we're heading to?

Yes, this topic was discussed thoroughly last year.

···

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

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:39:12, Anton Zhilin (antonyzhilin@gmail.com <mailto:antonyzhilin@gmail.com>) schrieb:

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.

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

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.

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

That’s what I thought too. That’s also the case I showed a simple example how we could solve the giant list with a typealias even if the pipe | operator would be exclusive for anything that conforms to Error.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:16:30, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

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

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.

So the typed throws are going to be limited to a single error type, is that the direction we're heading to?

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:39:12, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

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.

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

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, I couldn’t follow every thread. I simply couldn’t get that fact from the given context of the first post by Anton. :) Just forget everything I mentioned about typealias, because it was based on the assumption of an error list.

Anyways +1 for typed throws. The syntax throws(T) and rethrows(T) is fine by me.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:45:55, Matthew Johnson (matthew@anandabits.com) schrieb:

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

So the typed throws are going to be limited to a single error type, is that the direction we're heading to?

Yes, this topic was discussed thoroughly last year.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:39:12, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

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.

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

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.

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

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.

Imho we should remove the restriction that you can only throw Error-conforming types if typed throws are added:
It's a compatibility feature, and if you manually declare what can be thrown, you should be allowed to break with Objective-C.

As Error has no requirements at all, it looks like harassment, as its whole magic is neither visible nor necessary.

I disagree with that, it works if you only have a single function parameter type that throws an error, but if there are more than one inferring the type won’t be possible anymore: func foo(_: () throws(T) -> Void, _: () throws(S) -> Void) rethrows(S) (here, we’re assuming that T is handled inside foo).

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 21:17:42, Kevin Nattinger (swift@nattinger.net) schrieb:

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

Sorry, I couldn’t follow every thread. I simply couldn’t get that fact from the given context of the first post by Anton. :) Just forget everything I mentioned about typealias, because it was based on the assumption of an error list.

Anyways +1 for typed throws. The syntax throws(T) and rethrows(T) is fine by me.

I feel like rethrows can and should be inferred. It’s just a pass-through.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:45:55, Matthew Johnson (matthew@anandabits.com) schrieb:

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

So the typed throws are going to be limited to a single error type, is that the direction we're heading to?

Yes, this topic was discussed thoroughly last year.

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 20:39:12, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

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.

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

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.

_______________________________________________
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

Wouldn’t that mean that you couldn’t use your Swift library in Objective-C anymore, at least the error type as an NSError?

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 21:16:57, Tino Heth (2th@gmx.de) schrieb:

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.

Imho we should remove the restriction that you can only throw Error-conforming types if typed throws are added:
It's a compatibility feature, and if you manually declare what can be thrown, you should be allowed to break with Objective-C.

As Error has no requirements at all, it looks like harassment, as its whole magic is neither visible nor necessary.

I meant to say “… more than one throwing error type …”

···

--
Adrian Zubarev
Sent with Airmail

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

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. ;)

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

Several proposals will follow this one: allowing multiple error types,
removing Error, replacing rethrows, etc.
Those topics are more controversial, but fortunately for them, they mostly
add on top of the core feature being discussed.
So IMO, if a detail can be split into its own proposal, we should just do
it and forget about it for a little while.

···

2017-02-17 23:16 GMT+03:00 Tino Heth <2th@gmx.de>:

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.

Imho we should remove the restriction that you can only throw
Error-conforming types if typed throws are added:
It's a compatibility feature, and if you manually declare what can be
thrown, you should be allowed to break with Objective-C.

As Error has no requirements at all, it looks like harassment, as its
whole magic is neither visible nor necessary.

Wouldn’t that mean that you couldn’t use your Swift library in Objective-C anymore, at least the error type as an NSError?

That's the meaning of "break with Objective-C" here ;-) — but note that I wrote about allowing to do so, not forcing:
Now, we can only declare that something is thrown, and as interoperability is quite important, we have to assume it is needed.
As soon as you declare exactly what will be thrown, it should be up to you to decide if you need NSError-bridging.

Just to rephrase this a little bit.

- The list of thrown errors would not be part of the function’s signature. Instead, there is an ABI invariant that it throws an Error, and that it remains "switch-compatible". That’s a loose definition, but it covers things like throwing non-public Errors and later making them public while retaining ABI compatibility.
- It’s up to the library developer to maintain that invariant. They can remove errors, refine errors (e.g. “MyError” -> “MyError.{errorOne, errorThree}”), hide or reveal previously-hidden errors.
- If the errors are listed exhaustively, the compiler will generate an implicit catch-all to trap in case the library violates this invariant. This means they can become more specific while retaining ABI compatibility.

- We would put this information in the structured comments, because it can get long and complex and is always optional
- The compiler would ensure that their documentation is accurate, as it does any other part of the language.

- Karl

···

On 18 Feb 2017, at 16:27, Karl Wagner <karl.swift@springsup.com> wrote:

- 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

+1

-Chris

···

On Feb 17, 2017, at 11:29 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 17, 2017, at 1:24 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Let's not bring bikeshedding the commonly proposed and rejected union spelling into this.
Typed throws would be a nice addition, assuming that the core team finds it in scope for phase 2. It seems only logical that any type can be thrown (i.e. conforms to Error) should be permitted to be listed in `throws()`.

Agree. Typed throws should have a single thrown type. Making it more convenient to throw and catch more than one error type with typed throws without having to manually create a wrapper is an orthogonal issue. David Owens convinced me of this last year when we had a thread on the topic.

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

+1

···

Sent from my iPad

On Feb 19, 2017, at 11:34 AM, Brandon Knope via swift-evolution <swift-evolution@swift.org> wrote:

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

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

It’s expected that if you need resilience, then you will throw an “open”
enum. Essentially, we pass resilience of typed throws on to those who will
hopefully establish resilience of enums.

If you prefer separate error types, then declare a base protocol for all
your error types and throw a protocol existential. You won’t even need
default case in switches, if closed protocols make it into the language.

I don’t like any solution that is based on comments. I think that compiler
should always ignore comments.

···

2017-02-18 18:27 GMT+03:00 Karl Wagner <razielim@gmail.com>:

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

> 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.

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.

+1 here. It becomes wrapping over wrapping over wrapping. Try doing a big
app in Java (i.e. some kind of layered server) and you'll understand
everything. Ones who tried and still want it - well, there are different
tastes out there.

···

On Mon, 27 Feb 2017 at 8:44 Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

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

--
-Dave

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

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.

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.

It seems to me that both can be true. It is up to the author to
determine which applies in a given use case. I really don't think
Swift should require those who understand where it is beneficial to
use non-throwing functions that return a Result type if they wish to
realize the benefits when they are relevant.

My understanding of Chris's meaning of progressive disclosure is that
the language should have powerful and general tools at its foundation
that can be ignored until they are necessary for a particular problem.

For what problem is this feature necessary?

I was speaking about progressive disclosure generally. You are right
that typed errors are not *necessary* for any problems. Of course
that can be said about many language features. Perhaps I should have
said "until they add value or clarity”.

In this case the powerful and general tool is typed errors. The
syntactic sugar allowing you to omit a type and therefore throw any
error (typed as Error) allows users to ignore the more general tool
when it isn't providing value.

We should focus on educating users who wish to use this tool about the
tradeoffs involved and how to think about what error type might be
appropriate in different use cases rather than introduce an arbitrary
(i.e. not technical) limitation prohibiting typed errors just because
they can be badly used. Understanding the tradeoffs is certainly not
a beginner topic, but there are plenty of things in Swift that are not
beginner topics.

In my experience, given an opportunity to encode something in the type
system or categorize and annotate things using language constructs, most
users will. That's usually a great instinct, but not in this case, IMO.
The hard problems of error recovery involve maintaining your program
invariants, but this feature contributes nothing toward that end. The
one thing it *could* improve is the quality of error reporting to end
users, but in practice that ends up devolving to dynamic lookups due to
the need for localization. So we truly gain very little from encoding
this feature in the type system.

Error reporting and recovery are precisely where type errors can help.
I’ll give an example below.

This particular feature is viral in the same sense as C++ const, so I
predict it will either see widespread use, which IMO would be harmful,
or everyone will learn to avoid it. Either way, it seems like a bad
investment for the language.

I understand why you might make this comparison but I think there is
an important difference.

If I receive a const input I can only give that input to other things
that take a const. It restricts my dependencies (what I can do with
the input), or conversely I have to abandon const or cast it away (!)
because my dependency isn’t declared as taking const.

With typed errors the situation is a little bit different. My
signature is much more independent from that of my dependencies.

For example, I might have some dependencies which my users would
prefer to not be tightly coupled with that happen to throw errors.
For example, maybe I depend on an IO library and a parsing library.
These dependencies are subject to change in the future. Rather than
let the errors propagate directly I wrap them in `enum MyLIbraryError
{ case IOError(Error); case ParsingError(Error) }`.

This could provide a meaningful abstraction / grouping that helps the
client know what might or might not solve the problem.

And it might not. It might turn out that something you have to call
throws an error that doesn't fit into either case. Remember, the things
you call may be closures or methods supplied to you by your clients.
Then you have to resort to the equivalent of casting away const.

It might be worth exposing the error type to callers giving them an
easier way to cover different high-level causes of an error. In this
example, I can perform the grouping even if my dependencies throw
untyped errors.

Lots of things might be worth doing. I'm saying you need a much more
compelling case that this *is* worth doing before I'd personally risk
adding it to the language, when experience and, frankly, plain logic,
show this kind of feature to have been problematic.

Even if this enum is exposed resiliently it still provides a
significantly better experience for my users than an untyped error.
They know that these are the major categories of error that they
should be thinking about when using my library and have an easy way to
catch each kind of error.

You can give users nearly the same value by simply documenting that
your library throws MyLibraryError, without any of the risks.

They might also decide to automatically retry an operation or not
depending on what kind of error occurred.

This kind of wrapping and grouping can obviously be performed without
typed errors but IMO is better with typed errors where the types never
get out of sync with the code (as documentation can)

With resilient enums, of course they do get out of sync.

and we have better tool integration. Also, when working in the same
module (or with a non-resilient library error) we get the ability to
catch errors exhaustively without needing an “unknown error” clause.
This *does not* mean every individual error is handled directly, but
that they are meaningfully grouped in a way that allows us to cover
each possible grouping.

Any problems this feature has with being viral would be one of user
expectations and community culture. It wouldn’t be a strictly
technical virality that sets up a kind of dependency between users of
my function and implementation details of my function.

I'm sorry, I don't see any substantive difference, based on what you've
written here, between this feature and const.

Let me give it one more shot and then I’ll drop it. :)

Const is viral because if an API does not declare its arguments const it cannot be used by a caller who has a const argument. It is required in order to make an API as generally useful as possible.

Typed errors are not viral in this way because no callers are prevented from calling an API regardless of whether it declares error types or just throws Error like we have today. Pressure to declare error types in your signature in order to make your function as generally useful as possible does not exist. Each function is free to declare error types or not according to the contract it wishes to expose.

An argument can be made that community expectations might develop that good APIs should declare error types and they could be considered viral in this sense because any API that is simply declared `throws` is dropping type information. But I think this overstates the case. The community appears to be very sensitive to the problems that can arise when error types are too concrete, especially across module boundaries. I think we can learn to use the tool where it works well and to avoid it where it causes problems.

···

On Feb 27, 2017, at 4:20 PM, Dave Abrahams <dabrahams@apple.com> wrote:
on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com <http://matthew-at-anandabits.com/&gt;&gt; wrote:

On Feb 27, 2017, at 10:48 AM, Dave Abrahams <dabrahams@apple.com> wrote:
on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:

On Feb 27, 2017, at 12:32 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

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

--
-Dave

I disagree with that, it works if you only have a single function parameter type that throws an error, but if there are more than one inferring the type won’t be possible anymore: func foo(_: () throws(T) -> Void, _: () throws(S) -> Void) rethrows(S) (here, we’re assuming that T is handled inside foo).

Well, isn't it actually incorrect that "rethrows" is a property of the function, and not of its parameters?

Just imagine this

func catches(catchMe: () throws -> Void, throwMe: () throws -> Void) rethrows {
  try? catchMe()
  try throwMe()
  print("Nothing thrown")
}

func doesThrow() throws {
  throw NSError()
}

func doesntThrow() {
  print("I'm a nice function")
}

try catches(catchMe: doesThrow, throwMe: doesntThrow)

The last call can't throw, but the compiler still needs the "try" on it.
I guess in real-world code, this isn't an issue — but still, it's wrong… so if we want a really sophisticated model for error handling, imho this should be addressed.

Maybe we should also take into account that a higher-order function might catch some errors, and only rethrow for special cases, and this adds complexity as well?
In this case, each function-parameter would require a full list a errors that are filtered out, or even a translation-table… I guess most developers would agree that this is overkill, and that keeping things simple has merit as well.