[Pitch] Typed throws

David, IMHO, all you say is absolutely true and typed throws might work well, but in theoretic idealistic world. In reality though, you end having more exception types than data types, feature cost rises exponentially and the code becomes cluttered with all the wrapping.

I seriously don't understand why would one even think of this feature after it's proven a bad practice by Java community. Even Java based languages (i.e. Scala) have dropped this "feature".

I share Daniel's concerns 100%. The Swift community is so (rightfully) concerned about stability right now, and all I can see is how this could tie the hands of a future API author.

I'd love to have those fears mollified. If Swift can noticeably improve on the typed throws model (through type checking, conversion, some ABI guarantees for changing errors, etc.), like Swift has for a lot of features other languages have tried and had trouble with, we should definitely move forward with typed throws. If we can't, it does a lot to hurt what I think is the great success of Swift's error-handling model.

Do you have a sense of the criteria you would use to determine whether the improvements are satisfactory to you or not?

···

On Feb 27, 2017, at 4:44 PM, Zach Waldowski via swift-evolution <swift-evolution@swift.org> wrote:
On Mon, Feb 27, 2017, at 04:05 PM, Daniel Leping via swift-evolution wrote:

Zach
zach@waldowski.me <mailto:zach@waldowski.me>

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

I share Daniel's concerns 100%. The Swift community is so (rightfully)
concerned about stability right now, and all I can see is how this could
tie the hands of a future API author.

I'd love to have those fears mollified. If Swift can noticeably improve
on the typed throws model (through type checking, conversion, some ABI
guarantees for changing errors, etc.), like Swift has for a lot of
features other languages have tried and had trouble with, we should
definitely move forward with typed throws. If we can't, it does a lot to
hurt what I think is the great success of Swift's error-handling model.

Zach

zach@waldowski.me

···

On Mon, Feb 27, 2017, at 04:05 PM, Daniel Leping via swift-evolution wrote:

David, IMHO, all you say is absolutely true and typed throws might
work well, but in theoretic idealistic world. In reality though, you
end having more exception types than data types, feature cost rises
exponentially and the code becomes cluttered with all the wrapping.

I seriously don't understand why would one even think of this feature
after it's proven a bad practice by Java community. Even Java based
languages (i.e. Scala) have dropped this "feature".

Well, as Dave pointed, you can very rarely recover from an error, which IMO
is absolutely true.

If your operation fails you don't really care unless you can recover. And
you know your cases, which you can recover from (in reality one usually
does it in optimization phase, though).

What else do you need the type of error for at the very end of your call
stack? In 90% you will tell the user "ah, sorry, something happened. Come
back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be
presented to users. They are for developers to read the log/crash
report/whatever else and analyze it. Most of the errors are for debugging
purposes.

Disagreed, sorry. When I said that recovery is rare, I meant that it is
generally done in a very few functions, not that it is rarely possible
at all. If something goes wrong when editing a document, for example
full or network failure, you simply recover its previous state from the
undo stack. Errors are *for* recoverable conditions. If you're not
going to recover you might as well trap and save all the unwinding and
invariant preservation logic.

···

on Mon Feb 27 2017, Daniel Leping <daniel-AT-crossroadlabs.xyz> wrote:

I don't want to deal with cumbersome code the purpose of which is to just
"obey the language rules". Unless I know how to recover I would rethrow it.
Than catch at the top of the stack and log + show the user a nice "sorry"
message without getting techy.

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

> On Feb 27, 2017, at 5:01 PM, Dave Abrahams <dabrahams@apple.com> wrote:
>
>
> on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:
>
>>> On Feb 27, 2017, at 4:20 PM, Dave Abrahams <dabrahams@apple.com> >> wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const.
Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much
more similar to any other kind of value wrapper a library might create in
order to shield its users from being coupled to its implementation details
/ dependencies. This is not a way *around* the type system in the sense
that casting away const is. It is a way of *using* the type system
(hopefully) to your advantage.

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

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

--
-Dave

As I've tried to point out, it's not that simple. You have to consider
the complexity cost of adding a feature, whether people will be able to
understand how to use it properly and when to avoid it, and what effect
that use will have on the programming ecosystem.

···

on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:

If you don't believe typed errors will improve your code or if you
just don't want to deal with typed errors, just don't use them!

--
-Dave

Well, as Dave pointed, you can very rarely recover from an error, which IMO is absolutely true.

If your operation fails you don't really care unless you can recover. And you know your cases, which you can recover from (in reality one usually does it in optimization phase, though).

What else do you need the type of error for at the very end of your call stack? In 90% you will tell the user "ah, sorry, something happened. Come back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be presented to users. They are for developers to read the log/crash report/whatever else and analyze it. Most of the errors are for debugging purposes.

I don't want to deal with cumbersome code the purpose of which is to just "obey the language rules". Unless I know how to recover I would rethrow it. Than catch at the top of the stack and log + show the user a nice "sorry" message without getting techy.

In order to provide a helpful experience to end users an app needs to know about what might have caused the error and therefore what might (or might not) resolve it, allowing the operation to succeed on a subsequent attempt. This can influence the strategy an app uses to avoid bothering the user if it might be resolvable without user intervention and can also influence the content of the message presented to users if that is necessary.

Indeed, many apps don't bother with this kind of detail and just treat all errors the same. Personally, I find that to be an unfortunate state of affairs both as a user and as a developer.

Types can be useful in conveying this kind of information and abstracting low level details that are not helpful at higher levels in the stack. Of course types are not the only way to convey this information. But my experience is that untyped errors often result in libraries with poor documentation of error cases and not enough consideration of the error handling experience of users of the library in general. That makes it very difficult to handle errors well. I have experiences like this in several languages and on several platforms, including Apple's.

Typed errors are certainly no panacea but I believe they can be an important tool. Experience in other languages has shown that to be the case for many people writing many kinds of software.

If you don't believe typed errors will improve your code or if you just don't want to deal with typed errors, just don't use them! You will be able to continue using untyped throws just as you do today. You can even do this if you use libraries that throw typed errors.

···

Sent from my iPad

On Feb 27, 2017, at 7:19 PM, Daniel Leping <daniel@crossroadlabs.xyz> wrote:

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

> On Feb 27, 2017, at 5:01 PM, Dave Abrahams <dabrahams@apple.com> wrote:
>
>
> on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:
>
>>> On Feb 27, 2017, at 4:20 PM, Dave Abrahams <dabrahams@apple.com> wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const. Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much more similar to any other kind of value wrapper a library might create in order to shield its users from being coupled to its implementation details / dependencies. This is not a way *around* the type system in the sense that casting away const is. It is a way of *using* the type system (hopefully) to your advantage.

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

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

If you don't believe typed errors will improve your code or if you
just don't want to deal with typed errors, just don't use them!

As I've tried to point out, it's not that simple. You have to consider
the complexity cost of adding a feature, whether people will be able to
understand how to use it properly and when to avoid it, and what effect
that use will have on the programming ecosystem.

I guess I believe the community is pretty wise about this kind of thing and have confidence in it to use this well especially if we are willing to refine the model as we learn from experience. Who knows, maybe I'm too optimistic.

···

Sent from my iPad

On Feb 27, 2017, at 8:21 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Mon Feb 27 2017, Matthew Johnson <matthew-AT-anandabits.com> wrote:

--
-Dave

Add more layers, and it can be very mysterious why a call failed. Java at least captures stack traces in this case to aid in technical support in diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different subsystems being handled as a category, such as having a catch block handle RecoverableError generically

An interesting solution that has emerged in Ruby to keep library authors from wrapping exceptions is by decorating the existing exception. Exceptions are caught via pattern matching (same as in Swift), so rather than wrap an extension, they extend the error instance with a library-specific module (e.g. swift protocol). So while the error may be a IOError in ruby, you can still catch it via ‘rescue JSONError’

If I understand this correctly it sounds like introducing a library would create protocols to categorize errors and add retroactive conformances to these protocols for errors thrown by its dependencies? That is an interesting approach. But it requires knowing the concrete types of the possible errors all the way down the stack (you can’t add a conformance to an existential). This seems very problematic to me, especially in a language where creating new error types is as easy as it is in Swift.

I believe it is something that even Objective C can’t do; extend a single instance of a type to support a protocol.

You can still do really interesting things with categories of errors, such as extend existing types to support a protocol, and putting categorization logic on third party errors yourself.

That breaks though if every library does

struct MyError: Error {
   let innerError: Error
}

in order to have a typed throw.

Error handling is messy, there’s no doubt about that. I would like to have as many tools at my disposal as possible. Error types is one of those tools.

I’m still trying to mentally put together the elevator pitch for typed throws. How do I explain to new/junior developers and developers coming from other languages when to do typed throws, and when not to?

-DW

···

On Feb 27, 2017, at 2:08 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent from my iPad

Well, as Dave pointed, you can very rarely recover from an error, which IMO is absolutely true.

If your operation fails you don't really care unless you can recover. And you know your cases, which you can recover from (in reality one usually does it in optimization phase, though).

What else do you need the type of error for at the very end of your call stack? In 90% you will tell the user "ah, sorry, something happened. Come back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be presented to users. They are for developers to read the log/crash report/whatever else and analyze it. Most of the errors are for debugging purposes.

I don't want to deal with cumbersome code the purpose of which is to just "obey the language rules". Unless I know how to recover I would rethrow it. Than catch at the top of the stack and log + show the user a nice "sorry" message without getting techy.

In order to provide a helpful experience to end users an app needs to know about what might have caused the error and therefore what might (or might not) resolve it, allowing the operation to succeed on a subsequent attempt. This can influence the strategy an app uses to avoid bothering the user if it might be resolvable without user intervention and can also influence the content of the message presented to users if that is necessary.

Errors are certainly useful, but the key distinction (IMO) is that callers should never rely on an error happening or not. They are typically highly implementation and hence version-specific. I think that means specific errors shouldn’t be part of the function signature.

That doesn’t mean you can’t handle the error. A function that throws is one that reserves the right to fail; maybe you can resolve the problem, but maybe you can’t. That means your enclosing operation also needs to be prepared to fail for reasons beyond _its_ control, and so on. At some point your entire UI-level sub-operation (like opening the file, downloading the data from the network) just fails, and you decide whether or not the user needs to be told of that and how to do so.

Indeed, many apps don't bother with this kind of detail and just treat all errors the same. Personally, I find that to be an unfortunate state of affairs both as a user and as a developer.

Types can be useful in conveying this kind of information and abstracting low level details that are not helpful at higher levels in the stack. Of course types are not the only way to convey this information. But my experience is that untyped errors often result in libraries with poor documentation of error cases and not enough consideration of the error handling experience of users of the library in general. That makes it very difficult to handle errors well. I have experiences like this in several languages and on several platforms, including Apple's.

I’m not sure that types are really the best abstraction for “the list of errors that can be thrown by this function”. They’re fine for error themselves, but not for per-function error-lists IMO. Most of our types are way too rigid for this to be easy to live with.

If I understand what you’re saying, the core problem can be summarised as: "I don’t know/can’t easily communicate which errors this function throws”. Really, it is a documentation problem; it's too onerous to document every individual throwing function, even with our very pretty markdown-like syntax. I’m just not sure that the relatively rigid type-system is the solution.

Now, if we look at this from a documentation perspective: obviously, the compiler _typically_ can't generate documentation for you, because it doesn’t know what your types/functions are meant for. Error documentation, though, is different: the compiler *can* often know the specific errors which get thrown (or rethrown from a call to another function from whom it can get that information); and even when it can’t know that, often it can at least know the specific type of error. We could enhance the compiler libraries to track that; I spent half a day modifying the existing error-checker in Sema to prove the concept and it’s close.

Swift libraries don’t have header files, so third-parties will only ever see your documentation as generated by some tool which integrates the compiler. That means the compiler can “improve” the documentation when it is lacking. For yourself, if you care about explicitly writing out the errors which get thrown and you’re not working with an IDE, you could enable compiler-checking of your documentation comments. We can invent new shorthands to make it easier to list all of your various errors (individual cases, entire types, and maybe some private “other errors”), isolated from the rest of the language. For example:

/// - throws: MyError.{errorOne, errorThree}, AnotherError.*, *
func doSomething() throws

Maybe you can think of other ways we could help the documentation problem from the compiler side?

Typed errors are certainly no panacea but I believe they can be an important tool. Experience in other languages has shown that to be the case for many people writing many kinds of software.

If you don't believe typed errors will improve your code or if you just don't want to deal with typed errors, just don't use them! You will be able to continue using untyped throws just as you do today. You can even do this if you use libraries that throw typed errors.

If it’s the wrong solution to the problem, it does make the language worse overall.

···

On 28 Feb 2017, at 02:43, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
On Feb 27, 2017, at 7:19 PM, Daniel Leping <daniel@crossroadlabs.xyz <mailto:daniel@crossroadlabs.xyz>> wrote:

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> On Feb 27, 2017, at 5:01 PM, Dave Abrahams <dabrahams@apple.com <mailto: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 4:20 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const. Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much more similar to any other kind of value wrapper a library might create in order to shield its users from being coupled to its implementation details / dependencies. This is not a way *around* the type system in the sense that casting away const is. It is a way of *using* the type system (hopefully) to your advantage.

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

_______________________________________________
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

IMHO, there are two kinds of responses to errors - a specific response,
and a general one. Only the calling code knows how it will deal with
errors, so a “typed throws” is the function guessing possible calling
code behavior.

The problem is, that gives four possible combinations - two where the
function guesses correctly, and two where it doesn’t. The most damaging
is when it suspects a caller doesn’t care about the error, when the
caller actually does. This is unwanted wrapping.

To provide an example, imagine a library that parses JSON. It has several
errors indicating JSON syntactic errors, and an “other” for representing
errors on the input stream. It wraps the input stream errors so that it
can provide a closed set of errors to the caller.

The caller is responsible for returning a data set. It doesn’t think that
code calling ‘it” cares about JSON syntactic errors, merely that the
object was not able to be restored. It returns its own wrapped error.

However, the original caller knows it is loading from disk. If the
problem is due to an issue such as access permissions, It has to know
implementation details of the API it called if it wishes to dive through
the wrapped errors to find out if the problem was filesystem related.

Add more layers, and it can be very mysterious why a call failed. Java at
least captures stack traces in this case to aid in technical support in
diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different
subsystems being handled as a category, such as having a catch block
handle RecoverableError generically

Fwiw, I think wrapping errors is something that people are sometimes going
to want to do regardless of whether they are typed or not. Maybe the
solution is to better support wrapping errors by focusing on the problems
that wrapping causes. For example, we could do something like this to make

Just to clarify, do you think about something like this? :
(pseudocode, sorry for mistakes)

func foo() throws {}

func bar() throws {}

enum BazError: Error { case baz1, case baz2 }
func baz() throws(BazError) {..}

enum BatError: Error {
   case fooRelatedError(SomeType1)
   case barOrBazRelatedError
   case specialErrorOne(Int)
   case specialErrorTwo(String)
}

func bat() throws(BatError) {
   do {
     try foo()
   }
   catch {
     // underlyingError will be injected
     rethrow .fooRelatedError(SomeType1())
   }

   do {
     try bar()
     try baz()
   }
   catch {
     // underlyingError will be injected
     rethrow .barOrBazRelatedError
   }

   ..
   if flag1 { throw .specialErrorOne(intValue) }
   ..
   if flag2 { throw .specialErrorTwo(stringValue) }
   ..
}

and then

func test() {
   do {
     ...
     try bat()
     ...
   }
   catch let e as BatError {
     switch e {
       case fooRelatedError(let some) : { print(some, e.underlyingError) }
       case barOrBazRelatedError : {
    print("something with bar or baz, so try this:..")

    if let bazError = e.underlyingError as BazError {
      switch bazError {.....}
    } else {
      // do something about "bar" error
    }
  }
       case specialErrorOne(let i) : { print(i) }
       case specialErrorTwo(let s) : { print(s) }
     }

     log(e.fullErrorStackDescription) // BatError.description + underlyingError.description + underlyingError.underlyingError.description etc
   }...
}

?

···

On 28.02.2017 0:40, Matthew Johnson via swift-evolution wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

it easier to get at the original error:

protocol Error {
  // The error directly underlying this error.
  // Ideally the compiler would synthesize an implementation for enums
conforming to `Error`
  // If `self` is a case that has an associate value which is or conforms
to `Error` that error would be returned, otherwise `nil` would be returned.
  var underlyingError: Error? { get }

  // The original error underlying *all* layers of wrapping.
  // If underlyingError is non-nil this is also non-nil.
  var originalError: Error { get }
}
extension Error {
    var underlyingError: Error? {
      return nil
    }
    var originalError: Error {
      return underlyingError?.originalError ?? underlyingError ?? self
    }
}

We could even provide syntactic sugar for catch sites that want to deal
with the original error rather than the wrapped error if that is an
important use case.

An interesting solution that has emerged in Ruby to keep library authors
from wrapping exceptions is by decorating the existing exception.
Exceptions are caught via pattern matching (same as in Swift), so rather
than wrap an extension, they extend the error instance with a
library-specific module (e.g. swift protocol). So while the error may be
a IOError in ruby, you can still catch it via ‘rescue JSONError’

Trying to specify the exact errors becomes even more destructive with
protocols and closures, where the person defining the interface knows
neither which errors the implementor of the call will throw, nor
necessarily if the caller will want to implement specific behavior on
those errors. This in my personal Java coding experience almost always
leads to wrapping in some protocol-specific Exception type which exposes
minimal information to the caller, or exposing your errors in some
unrelated type like IOException which was declared based on the author’s
experience of possible exceptions.

-DW

On Feb 27, 2017, at 5:19 AM, Daniel Leping via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

    on Fri Feb 17 2017, Joe Groff <swift-evolution@swift.org >>> <mailto: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.

    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.

    --
    -Dave

    _______________________________________________
    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

_______________________________________________
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

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.

I agree. And in general, this sort of thing is exactly my core concern about adding typed throws to the language: I am completely certain that many programmers will add typed throws annotations because they're programmers and thus, well, probably a little obsessive/compulsive, and they're trying to precisely document the behavior of their function without necessarily thinking about the usefulness of that information for their clients and (if they're writing a library; and really you should almost always be writing code as if you're writing a library) whether they're actually willing to commit to that behavior in their interface. For those programmers, typed throws is just going to box them in and force them into anti-patterns in the long run.

As you know, I still think that adding typed throws is the right thing to do. I understand your concern about “the feature could be misused” but the same thing is true about many other language features.

That's fair, but I do think there's an important difference here. The way I see it, typed-throws is really something of an expert feature, not because it's at all difficult to use, but the reverse: because it's easy to use without really thinking about the consequences. (And the benefits are pretty subtle, too.) I'm not saying that we should design it to be hard to use, but I think maybe it shouldn't immediately suggest itself, and it especially shouldn't come across as just a more specific version of throws.

One thing you didn’t mention is that boxing thrown values in an existential requires allocation in the general case. This may be unacceptable for some classes of Swift application (in the embedded / deep systems space) or simply undesirable because of the performance implication.

So, the performance implication cuts both ways. We can design the ABI for typed-throws so that, say, the callee initializes some buffer that's passed into it. That's an ABI that will kill some potential allocations in deep systems code, no question about it. But in non-deep-systems code, we generally expect that error types will be resilient, which means that there are non-zero dynamic costs for allocating space on the stack for the error. (You can trade code size for performance, but the slow path is a function call to trigger the runtime layout of the type plus a couple of loads.) That space has to be allocated eagerly before the call, so it's paid even in the non-throwing path. And I can't think of anything we could do to shift that cost to the throw-site without either using a fixed-size buffer, requiring a second stack for the thread, or inventing some crazy CC where stack depth can change dynamically across a call.

John.

···

On Feb 20, 2017, at 6:04 PM, Chris Lattner <clattner@nondot.org> wrote:

On Feb 20, 2017, at 9:57 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Feb 19, 2017, at 3:04 PM, Anton Zhilin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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.

I agree. And in general, this sort of thing is exactly my core concern about adding typed throws to the language: I am completely certain that many programmers will add typed throws annotations because they're programmers and thus, well, probably a little obsessive/compulsive, and they're trying to precisely document the behavior of their function without necessarily thinking about the usefulness of that information for their clients and (if they're writing a library; and really you should almost always be writing code as if you're writing a library) whether they're actually willing to commit to that behavior in their interface. For those programmers, typed throws is just going to box them in and force them into anti-patterns in the long run.

In the vast majority of use-cases, clients are not going to exhaustively handle all errors — they will always have some generic fall-back. That is not pessimism, it's actually the natural result of the complicated world we live in, where code can fail for a huge host of reasons and most callers won't have meaningful special-case behavior for all of them. (On most operating systems, opening a file or a network connection can fail because you ran out of file descriptors. You're seriously telling me that you're going to add a special case to your error logic for that?) Go look at the actual error types that people use in most typed-throws situations and try to tell me I'm wrong — they probably have like twenty alternatives, and solidly a quarter or more of them will just be embedding some other arbitrarily-complex or stringly-typed error value.

I agree that overly specific API specs are a concern. There's still some documentational benefit to a typed error enum, even if it ultimately ends up containing a catch-all case, since it gives you the ability to also enumerate some number of discrete, realistically handleable cases. Sure, your network request running in an XPC service has infinitely many failure cases due to resource constraints or IPC failure or network chaos, but there's usually some number of domain-specific error cases too. And if we compare the proposed "you get to specify one error enum type" model to, say, Java or C++98's "you get to specify any number of error classes" model, I think that helps steer people away from the most grievous API mistakes in Java land, since instead of listing a closed set of concrete error classes directly in your API signature, you'll list those error cases in your enum definition, and in a resilient world, the enum will be "open" by default to external users, preventing it from being a permanent liability unless the user explicitly opted into closedness.

Realistically there are only rarely actionable error cases and usually only one or two; everything else just bubbles up to the top of the current operation where you retry or report a permanent failure. If the enum is open then you need a default catch-all error handling case anyway, so what benefit is there to typed throws that you don’t get with documentation comments?

I’m positive John is right about what will happen in practice.

Perhaps. Doc comments have all the usual problems of documentation not being compiled code so drifting out of date with implementation. While an open error type wouldn't guarantee that the errors declared are the ones thrown, it'll at least guarantee that they exist.

In the discussions around Rust's error handling conventions, they recognized this pattern of APIs either raising some number of layer-appropriate errors or carrying forward the failure modes of the layers below them, and they developed a convention for errors wrapping other errors. It would be reasonable to say we should do the same thing as part of the typed errors design. If we were to generalize enum subtyping beyond Optional, that might be one way to go about it, letting an enum wrap its underlying layers' failure cases as subtypes.

-Joe

Yeah but Rust has a macro system so error-chain makes re-wrapping trivial. A simple `chain_err(|| “my new error message”)` is enough to define and “throw” a new error that wraps the underlying error.

That was my point—they recognized error chaining/layering as a common phonemenon and provided affordances for it as part of their design. I was saying we should do the same if we investigate typed errors seriously. (Not sure why you mentioned macros—chain_err is just a regular method, and could be defined as an extension method on Error in swift today.)

-Joe

···

On Feb 20, 2017, at 11:20 PM, Russ Bishop <xenadu@gmail.com> wrote:

On Feb 20, 2017, at 1:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 20, 2017, at 9:57 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 19, 2017, at 3:04 PM, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

Add more layers, and it can be very mysterious why a call failed. Java at least captures stack traces in this case to aid in technical support in diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different subsystems being handled as a category, such as having a catch block handle RecoverableError generically

An interesting solution that has emerged in Ruby to keep library authors from wrapping exceptions is by decorating the existing exception. Exceptions are caught via pattern matching (same as in Swift), so rather than wrap an extension, they extend the error instance with a library-specific module (e.g. swift protocol). So while the error may be a IOError in ruby, you can still catch it via ‘rescue JSONError’

If I understand this correctly it sounds like introducing a library would create protocols to categorize errors and add retroactive conformances to these protocols for errors thrown by its dependencies? That is an interesting approach. But it requires knowing the concrete types of the possible errors all the way down the stack (you can’t add a conformance to an existential). This seems very problematic to me, especially in a language where creating new error types is as easy as it is in Swift.

I believe it is something that even Objective C can’t do; extend a single instance of a type to support a protocol.

Ok, I missed that it was a single instance, not all values with a given error type.

You can still do really interesting things with categories of errors, such as extend existing types to support a protocol, and putting categorization logic on third party errors yourself.

That breaks though if every library does

struct MyError: Error {
   let innerError: Error
}

in order to have a typed throw.

Did you see my suggestion for adding underlying and originating error properties to the `Error` protocol? That would solve the problem of every library doing this themselves and make it much easier to identify what happened at the bottom of the stack when you really need to know that (without manually extracting wrapped errors all the way down).

Error handling is messy, there’s no doubt about that. I would like to have as many tools at my disposal as possible. Error types is one of those tools.

I’m still trying to mentally put together the elevator pitch for typed throws. How do I explain to new/junior developers and developers coming from other languages when to do typed throws, and when not to?

IMO this is not that much different than any other aspect of API design. This isn’t so much an elevator pitch as an education that error handling is messy and complicated and requires just as much thought in designing an API as the happy path does. Nobody enjoys thinking about errors. This is an opportunity to help teach junior developers to think more carefully about them.

Help junior developers learn to think about users of an API and think about dependencies and coupling. When a user is calling this API how might they want to handle errors that might be thrown? How can the error type help facilitate that? On the other hand, when is an API exposing too much concrete type information to callers that creates unnecessary coupling? When that is the case, how can we best remove enough type information to avoid that coupling (i.e. wrapping, exposing an existential, etc)?

···

On Feb 27, 2017, at 11:04 PM, David Waite <david@alkaline-solutions.com> wrote:

On Feb 27, 2017, at 2:08 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-DW

Sent from my iPad

Well, as Dave pointed, you can very rarely recover from an error, which IMO is absolutely true.

If your operation fails you don't really care unless you can recover. And you know your cases, which you can recover from (in reality one usually does it in optimization phase, though).

What else do you need the type of error for at the very end of your call stack? In 90% you will tell the user "ah, sorry, something happened. Come back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be presented to users. They are for developers to read the log/crash report/whatever else and analyze it. Most of the errors are for debugging purposes.

I don't want to deal with cumbersome code the purpose of which is to just "obey the language rules". Unless I know how to recover I would rethrow it. Than catch at the top of the stack and log + show the user a nice "sorry" message without getting techy.

In order to provide a helpful experience to end users an app needs to know about what might have caused the error and therefore what might (or might not) resolve it, allowing the operation to succeed on a subsequent attempt. This can influence the strategy an app uses to avoid bothering the user if it might be resolvable without user intervention and can also influence the content of the message presented to users if that is necessary.

Errors are certainly useful, but the key distinction (IMO) is that callers should never rely on an error happening or not. They are typically highly implementation and hence version-specific. I think that means specific errors shouldn’t be part of the function signature.

That doesn’t mean you can’t handle the error. A function that throws is one that reserves the right to fail; maybe you can resolve the problem, but maybe you can’t. That means your enclosing operation also needs to be prepared to fail for reasons beyond _its_ control, and so on. At some point your entire UI-level sub-operation (like opening the file, downloading the data from the network) just fails, and you decide whether or not the user needs to be told of that and how to do so.

It is important that when this happens you need to be able to give the user as much useful information about what happened as possible.

Indeed, many apps don't bother with this kind of detail and just treat all errors the same. Personally, I find that to be an unfortunate state of affairs both as a user and as a developer.

Types can be useful in conveying this kind of information and abstracting low level details that are not helpful at higher levels in the stack. Of course types are not the only way to convey this information. But my experience is that untyped errors often result in libraries with poor documentation of error cases and not enough consideration of the error handling experience of users of the library in general. That makes it very difficult to handle errors well. I have experiences like this in several languages and on several platforms, including Apple's.

I’m not sure that types are really the best abstraction for “the list of errors that can be thrown by this function”. They’re fine for error themselves, but not for per-function error-lists IMO. Most of our types are way too rigid for this to be easy to live with.

If I understand what you’re saying, the core problem can be summarised as: "I don’t know/can’t easily communicate which errors this function throws”. Really, it is a documentation problem; it's too onerous to document every individual throwing function, even with our very pretty markdown-like syntax. I’m just not sure that the relatively rigid type-system is the solution.

If you have a better idea I would be thrilled to hear it, but documentation is not a good enough answer IMO.

Now, if we look at this from a documentation perspective: obviously, the compiler _typically_ can't generate documentation for you, because it doesn’t know what your types/functions are meant for. Error documentation, though, is different: the compiler *can* often know the specific errors which get thrown (or rethrown from a call to another function from whom it can get that information); and even when it can’t know that, often it can at least know the specific type of error. We could enhance the compiler libraries to track that; I spent half a day modifying the existing error-checker in Sema to prove the concept and it’s close.

But part of the point is that I *don’t* want to know every detail about errors that can be thrown. I want to have a reasonable higher-level view of *categories* of errors that might occur at runtime. I still want direct access to the concrete error that actually occurred (i.e. the underlying error) at runtime.

But I don’t want to be too tightly coupled to it and a full list of every possible error is a point-in-time is not really relevant. It might include information about dependencies of the library that I really shouldn’t know about. For example, let’s say my library has a dependency on network library XYZ and therefore it can throw XYZError. I don’t really want to know that or match on it. It might later change to ABC network library that throws ABCError. We need a stable abstraction that clients can depend on to reliably catch network error as distinct from parsing errors, for example, if that distinction is important to users of the library.

A type is required to allow the appropriate level of abstraction. The only question is whether my library’s error type which gives users that stable interface is visible in the type system or not. I think it should be. It might be a resilient enum or a protocol and therefore not allow exhaustive catch, but that is ok. The point is not exhaustive catch (although that’s nice when possible). The point is to have a stable, well designed and considered interface for errors coming out of the API that is clearly communicated to users of that API.

Swift libraries don’t have header files, so third-parties will only ever see your documentation as generated by some tool which integrates the compiler. That means the compiler can “improve” the documentation when it is lacking. For yourself, if you care about explicitly writing out the errors which get thrown and you’re not working with an IDE, you could enable compiler-checking of your documentation comments. We can invent new shorthands to make it easier to list all of your various errors (individual cases, entire types, and maybe some private “other errors”), isolated from the rest of the language. For example:

/// - throws: MyError.{errorOne, errorThree}, AnotherError.*, *
func doSomething() throws

Maybe you can think of other ways we could help the documentation problem from the compiler side?

I don’t think this is the right approach at all. As noted above, this is something that requires human judgement and design. The compiler cannot do that for us. The fact that we often *don’t* do it is a problem and compiler generated documentation is likely to make that problem worse.

Typed errors are certainly no panacea but I believe they can be an important tool. Experience in other languages has shown that to be the case for many people writing many kinds of software.

If you don't believe typed errors will improve your code or if you just don't want to deal with typed errors, just don't use them! You will be able to continue using untyped throws just as you do today. You can even do this if you use libraries that throw typed errors.

If it’s the wrong solution to the problem, it does make the language worse overall.

Obviously we disagree here.

···

On Feb 28, 2017, at 12:29 AM, Karl Wagner <razielim@gmail.com> wrote:

On 28 Feb 2017, at 02:43, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Feb 27, 2017, at 7:19 PM, Daniel Leping <daniel@crossroadlabs.xyz <mailto:daniel@crossroadlabs.xyz>> wrote:

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> On Feb 27, 2017, at 5:01 PM, Dave Abrahams <dabrahams@apple.com <mailto: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 4:20 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const. Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much more similar to any other kind of value wrapper a library might create in order to shield its users from being coupled to its implementation details / dependencies. This is not a way *around* the type system in the sense that casting away const is. It is a way of *using* the type system (hopefully) to your advantage.

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

_______________________________________________
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

IMHO, there are two kinds of responses to errors - a specific response,
and a general one. Only the calling code knows how it will deal with
errors, so a “typed throws” is the function guessing possible calling
code behavior.

The problem is, that gives four possible combinations - two where the
function guesses correctly, and two where it doesn’t. The most damaging
is when it suspects a caller doesn’t care about the error, when the
caller actually does. This is unwanted wrapping.

To provide an example, imagine a library that parses JSON. It has several
errors indicating JSON syntactic errors, and an “other” for representing
errors on the input stream. It wraps the input stream errors so that it
can provide a closed set of errors to the caller.

The caller is responsible for returning a data set. It doesn’t think that
code calling ‘it” cares about JSON syntactic errors, merely that the
object was not able to be restored. It returns its own wrapped error.

However, the original caller knows it is loading from disk. If the
problem is due to an issue such as access permissions, It has to know
implementation details of the API it called if it wishes to dive through
the wrapped errors to find out if the problem was filesystem related.

Add more layers, and it can be very mysterious why a call failed. Java at
least captures stack traces in this case to aid in technical support in
diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different
subsystems being handled as a category, such as having a catch block
handle RecoverableError generically

Fwiw, I think wrapping errors is something that people are sometimes going
to want to do regardless of whether they are typed or not. Maybe the
solution is to better support wrapping errors by focusing on the problems
that wrapping causes. For example, we could do something like this to make

Just to clarify, do you think about something like this? :
(pseudocode, sorry for mistakes)

Is the question about the ability to create `fullErrorStackDescription`? Yes, that would certainly be possible to implement in an extension.

I’m not sure why your examples use `rethrow` to rethrow the error instead of `throw`. Was that a mistake?

I also want to reiterate that deciding when and how to wrap errors is something that requires careful thought and judgment. The goal is to provide a stable and ergonomic way for callers to learn about and handle errors they are likely to care about. There will often be an “everything else” case and that’s ok.

As others have pointed out, the important thing is that it is easy to identify cases where I can *improve* UX by recovery or specific messaging to the user. The fact that there will usually be a default path is a given, but it’s best to avoid that path if possible.

···

On Feb 28, 2017, at 6:47 AM, Vladimir.S <svabox@gmail.com> wrote:
On 28.02.2017 0:40, Matthew Johnson via swift-evolution wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

func foo() throws {}

func bar() throws {}

enum BazError: Error { case baz1, case baz2 }
func baz() throws(BazError) {..}

enum BatError: Error {
case fooRelatedError(SomeType1)
case barOrBazRelatedError
case specialErrorOne(Int)
case specialErrorTwo(String)
}

func bat() throws(BatError) {
do {
   try foo()
}
catch {
   // underlyingError will be injected
   rethrow .fooRelatedError(SomeType1())
}

do {
   try bar()
   try baz()
}
catch {
   // underlyingError will be injected
   rethrow .barOrBazRelatedError
}

..
if flag1 { throw .specialErrorOne(intValue) }
..
if flag2 { throw .specialErrorTwo(stringValue) }
..
}

and then

func test() {
do {
   ...
   try bat()
   ...
}
catch let e as BatError {
   switch e {
     case fooRelatedError(let some) : { print(some, e.underlyingError) }
     case barOrBazRelatedError : {
    print("something with bar or baz, so try this:..")

    if let bazError = e.underlyingError as BazError {
      switch bazError {.....}
    } else {
      // do something about "bar" error
    }
  }
     case specialErrorOne(let i) : { print(i) }
     case specialErrorTwo(let s) : { print(s) }
   }

   log(e.fullErrorStackDescription) // BatError.description + underlyingError.description + underlyingError.underlyingError.description etc
}...
}

?

it easier to get at the original error:

protocol Error {
// The error directly underlying this error.
// Ideally the compiler would synthesize an implementation for enums
conforming to `Error`
// If `self` is a case that has an associate value which is or conforms
to `Error` that error would be returned, otherwise `nil` would be returned.
var underlyingError: Error? { get }

// The original error underlying *all* layers of wrapping.
// If underlyingError is non-nil this is also non-nil.
var originalError: Error { get }
}
extension Error {
   var underlyingError: Error? {
     return nil
   }
   var originalError: Error {
     return underlyingError?.originalError ?? underlyingError ?? self
   }
}

We could even provide syntactic sugar for catch sites that want to deal
with the original error rather than the wrapped error if that is an
important use case.

An interesting solution that has emerged in Ruby to keep library authors
from wrapping exceptions is by decorating the existing exception.
Exceptions are caught via pattern matching (same as in Swift), so rather
than wrap an extension, they extend the error instance with a
library-specific module (e.g. swift protocol). So while the error may be
a IOError in ruby, you can still catch it via ‘rescue JSONError’

Trying to specify the exact errors becomes even more destructive with
protocols and closures, where the person defining the interface knows
neither which errors the implementor of the call will throw, nor
necessarily if the caller will want to implement specific behavior on
those errors. This in my personal Java coding experience almost always
leads to wrapping in some protocol-specific Exception type which exposes
minimal information to the caller, or exposing your errors in some
unrelated type like IOException which was declared based on the author’s
experience of possible exceptions.

-DW

On Feb 27, 2017, at 5:19 AM, Daniel Leping via swift-evolution >>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

   on Fri Feb 17 2017, Joe Groff <swift-evolution@swift.org >>>> <mailto: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.

   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.

   --
   -Dave

   _______________________________________________
   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

_______________________________________________
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

When you're going to present a user with specific error message you don't
do it for all errors. First of all at the end of a call chain you will have
a couple of dozens of possible errors and only 2 or 3 will do really matter
for the end user. Would you list all the dozen in params? Probably not.
You'll just catch what matters and put the rest to the log with a default
"sorry" message.

What I mean is it's OK to be "unaware" why everything failed. What really
does matter is what you can deal with and what you can't (the rest). "The
rest" is usually a magnitude bigger, though.

I might agree, that listing some errors in the throws clause is useful
feature for documentation (even so, I think the docs should be in the
comments), but only and only if it can throw the listed errors + a bunch of
other stuff. In Java it's resolved with a terrible solution of RuntimeError
interface which I definitely suggest to avoid.

As a library developer I can say that even for a library it's a nightmare
and libraries will start wrapping system errors with their own if the trend
would become to list errors in middleware. My preference though is to have
all errors transparently passed in both roles, being a library developer
and a library user.

···

On Tue, 28 Feb 2017 at 8:29 Karl Wagner <razielim@gmail.com> wrote:

On 28 Feb 2017, at 02:43, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

Sent from my iPad

On Feb 27, 2017, at 7:19 PM, Daniel Leping <daniel@crossroadlabs.xyz> > wrote:

Well, as Dave pointed, you can very rarely recover from an error, which
IMO is absolutely true.

If your operation fails you don't really care unless you can recover. And
you know your cases, which you can recover from (in reality one usually
does it in optimization phase, though).

What else do you need the type of error for at the very end of your call
stack? In 90% you will tell the user "ah, sorry, something happened. Come
back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be
presented to users. They are for developers to read the log/crash
report/whatever else and analyze it. Most of the errors are for debugging
purposes.

I don't want to deal with cumbersome code the purpose of which is to just
"obey the language rules". Unless I know how to recover I would rethrow it.
Than catch at the top of the stack and log + show the user a nice "sorry"
message without getting techy.

In order to provide a helpful experience to end users an app needs to know
about what might have caused the error and therefore what might (or might
not) resolve it, allowing the operation to succeed on a subsequent
attempt. This can influence the strategy an app uses to avoid bothering
the user if it might be resolvable without user intervention and can also
influence the content of the message presented to users if that is
necessary.

Errors are certainly useful, but the key distinction (IMO) is that callers
should never rely on an error happening or not. They are typically highly
implementation and hence version-specific. I think that means specific
errors shouldn’t be part of the function signature.

That doesn’t mean you can’t handle the error. A function that throws is
one that reserves the right to fail; maybe you can resolve the problem, but
maybe you can’t. That means your enclosing operation also needs to be
prepared to fail for reasons beyond _its_ control, and so on. At some point
your entire UI-level sub-operation (like opening the file, downloading the
data from the network) just fails, and you decide whether or not the user
needs to be told of that and how to do so.

Indeed, many apps don't bother with this kind of detail and just treat all
errors the same. Personally, I find that to be an unfortunate state of
affairs both as a user and as a developer.

Types can be useful in conveying this kind of information and abstracting
low level details that are not helpful at higher levels in the stack. Of
course types are not the only way to convey this information. But my
experience is that untyped errors often result in libraries with poor
documentation of error cases and not enough consideration of the error
handling experience of users of the library in general. That makes it
very difficult to handle errors well. I have experiences like this in
several languages and on several platforms, including Apple's.

I’m not sure that types are really the best abstraction for “the list of
errors that can be thrown by this function”. They’re fine for error
themselves, but not for per-function error-lists IMO. Most of our types are
way too rigid for this to be easy to live with.

If I understand what you’re saying, the core problem can be summarised as:
"I don’t know/can’t easily communicate which errors this function throws”.
Really, it is a documentation problem; it's too onerous to document every
individual throwing function, even with our very pretty markdown-like
syntax. I’m just not sure that the relatively rigid type-system is the
solution.

Now, if we look at this from a documentation perspective: obviously, the
compiler _typically_ can't generate documentation for you, because it
doesn’t know what your types/functions are meant for. Error documentation,
though, is different: the compiler *can* often know the specific errors
which get thrown (or rethrown from a call to another function from whom it
can get that information); and even when it can’t know that, often it can
at least know the specific type of error. We could enhance the compiler
libraries to track that; I spent half a day modifying the existing
error-checker in Sema to prove the concept and it’s close.

Swift libraries don’t have header files, so third-parties will only ever
see your documentation as generated by some tool which integrates the
compiler. That means the compiler can “improve” the documentation when it
is lacking. For yourself, if you care about explicitly writing out the
errors which get thrown and you’re not working with an IDE, you could
enable compiler-checking of your documentation comments. We can invent new
shorthands to make it easier to list all of your various errors (individual
cases, entire types, and maybe some private “other errors”), isolated from
the rest of the language. For example:

/// - throws: MyError.{errorOne, errorThree}, AnotherError.*, *
func doSomething() throws

Maybe you can think of other ways we could help the documentation problem
from the compiler side?

Typed errors are certainly no panacea but I believe they can be an
important tool. Experience in other languages has shown that to be the
case for many people writing many kinds of software.

If you don't believe typed errors will improve your code or if you just
don't want to deal with typed errors, just don't use them! You will be
able to continue using untyped throws just as you do today. You can even
do this if you use libraries that throw typed errors.

If it’s the wrong solution to the problem, it does make the language worse
overall.

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

> On Feb 27, 2017, at 5:01 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 4:20 PM, Dave Abrahams <dabrahams@apple.com> > wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const.
Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much
more similar to any other kind of value wrapper a library might create in
order to shield its users from being coupled to its implementation details
/ dependencies. This is not a way *around* the type system in the sense
that casting away const is. It is a way of *using* the type system
(hopefully) to your advantage.

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

_______________________________________________
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

IMHO, there are two kinds of responses to errors - a specific response,
and a general one. Only the calling code knows how it will deal with
errors, so a “typed throws” is the function guessing possible calling
code behavior.

The problem is, that gives four possible combinations - two where the
function guesses correctly, and two where it doesn’t. The most damaging
is when it suspects a caller doesn’t care about the error, when the
caller actually does. This is unwanted wrapping.

To provide an example, imagine a library that parses JSON. It has several
errors indicating JSON syntactic errors, and an “other” for representing
errors on the input stream. It wraps the input stream errors so that it
can provide a closed set of errors to the caller.

The caller is responsible for returning a data set. It doesn’t think that
code calling ‘it” cares about JSON syntactic errors, merely that the
object was not able to be restored. It returns its own wrapped error.

However, the original caller knows it is loading from disk. If the
problem is due to an issue such as access permissions, It has to know
implementation details of the API it called if it wishes to dive through
the wrapped errors to find out if the problem was filesystem related.

Add more layers, and it can be very mysterious why a call failed. Java at
least captures stack traces in this case to aid in technical support in
diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different
subsystems being handled as a category, such as having a catch block
handle RecoverableError generically

Fwiw, I think wrapping errors is something that people are sometimes going
to want to do regardless of whether they are typed or not. Maybe the
solution is to better support wrapping errors by focusing on the problems
that wrapping causes. For example, we could do something like this to make

Just to clarify, do you think about something like this? :
(pseudocode, sorry for mistakes)

Is the question about the ability to create `fullErrorStackDescription`? Yes, that would certainly be possible to implement in an extension.

I’m not sure why your examples use `rethrow` to rethrow the error instead of `throw`. Was that a mistake?

I also want to reiterate that deciding when and how to wrap errors is something that requires careful thought and judgment. The goal is to provide a stable and ergonomic way for callers to learn about and handle errors they are likely to care about. There will often be an “everything else” case and that’s ok.

As others have pointed out, the important thing is that it is easy to identify cases where I can *improve* UX by recovery or specific messaging to the user. The fact that there will usually be a default path is a given, but it’s best to avoid that path if possible.

Well, I was trying to figure out how the some code could looks like with typed throws, taking into account your suggestion about built-in underlyingError and originalError props in Error protocol and the idea of "Maybe the solution is to better support wrapping errors by focusing on the problems that wrapping causes".
As for 'rethrow' it is not a mistake, but hypothetical use of the keyword to rethrow underlying error inside own error without boilerplate code, i.e. "better support wrapping", compare:

catch let e {
   // need to somehow inject the current e instance into our error instance
   // (or with some other syntax)
   throw BarError.fooRelatedError(SomeType1(), underlying: e)
}

and

catch {
   // 'rethrow' can clearly say that the current error instance will be injected
   // into our own error instance in underlyingError prop. we focuse only
   // on our own error instance
   rethrow fooRelatedError(SomeType1())
}

Sorry for not clarifying all this in first message.

···

On 28.02.2017 19:48, Matthew Johnson wrote:

On Feb 28, 2017, at 6:47 AM, Vladimir.S <svabox@gmail.com> wrote:
On 28.02.2017 0:40, Matthew Johnson via swift-evolution wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution >>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

func foo() throws {}

func bar() throws {}

enum BazError: Error { case baz1, case baz2 }
func baz() throws(BazError) {..}

enum BatError: Error {
case fooRelatedError(SomeType1)
case barOrBazRelatedError
case specialErrorOne(Int)
case specialErrorTwo(String)
}

func bat() throws(BatError) {
do {
   try foo()
}
catch {
   // underlyingError will be injected
   rethrow .fooRelatedError(SomeType1())
}

do {
   try bar()
   try baz()
}
catch {
   // underlyingError will be injected
   rethrow .barOrBazRelatedError
}

..
if flag1 { throw .specialErrorOne(intValue) }
..
if flag2 { throw .specialErrorTwo(stringValue) }
..
}

and then

func test() {
do {
   ...
   try bat()
   ...
}
catch let e as BatError {
   switch e {
     case fooRelatedError(let some) : { print(some, e.underlyingError) }
     case barOrBazRelatedError : {
    print("something with bar or baz, so try this:..")

    if let bazError = e.underlyingError as BazError {
      switch bazError {.....}
    } else {
      // do something about "bar" error
    }
  }
     case specialErrorOne(let i) : { print(i) }
     case specialErrorTwo(let s) : { print(s) }
   }

   log(e.fullErrorStackDescription) // BatError.description + underlyingError.description + underlyingError.underlyingError.description etc
}...
}

?

it easier to get at the original error:

protocol Error {
// The error directly underlying this error.
// Ideally the compiler would synthesize an implementation for enums
conforming to `Error`
// If `self` is a case that has an associate value which is or conforms
to `Error` that error would be returned, otherwise `nil` would be returned.
var underlyingError: Error? { get }

// The original error underlying *all* layers of wrapping.
// If underlyingError is non-nil this is also non-nil.
var originalError: Error { get }
}
extension Error {
   var underlyingError: Error? {
     return nil
   }
   var originalError: Error {
     return underlyingError?.originalError ?? underlyingError ?? self
   }
}

We could even provide syntactic sugar for catch sites that want to deal
with the original error rather than the wrapped error if that is an
important use case.

An interesting solution that has emerged in Ruby to keep library authors
from wrapping exceptions is by decorating the existing exception.
Exceptions are caught via pattern matching (same as in Swift), so rather
than wrap an extension, they extend the error instance with a
library-specific module (e.g. swift protocol). So while the error may be
a IOError in ruby, you can still catch it via ‘rescue JSONError’

Trying to specify the exact errors becomes even more destructive with
protocols and closures, where the person defining the interface knows
neither which errors the implementor of the call will throw, nor
necessarily if the caller will want to implement specific behavior on
those errors. This in my personal Java coding experience almost always
leads to wrapping in some protocol-specific Exception type which exposes
minimal information to the caller, or exposing your errors in some
unrelated type like IOException which was declared based on the author’s
experience of possible exceptions.

-DW

On Feb 27, 2017, at 5:19 AM, Daniel Leping via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

   on Fri Feb 17 2017, Joe Groff <swift-evolution@swift.org >>>>> <mailto: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.

   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.

   --
   -Dave

   _______________________________________________
   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

_______________________________________________
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

As you know, I still think that adding typed throws is the right thing to do. I understand your concern about “the feature could be misused” but the same thing is true about many other language features.

That's fair, but I do think there's an important difference here. The way I see it, typed-throws is really something of an expert feature, not because it's at all difficult to use, but the reverse: because it's easy to use without really thinking about the consequences. (And the benefits are pretty subtle, too.) I'm not saying that we should design it to be hard to use, but I think maybe it shouldn't immediately suggest itself, and it especially shouldn't come across as just a more specific version of throws.

Yeah, I agree that it will be appealing to people who don’t know better, but here’s the thing: the (almost certain) Swift design will prevent the bad thing from happening in practice.

Consider the barriers Swift already puts in place to prevent the bad thing (declaring an inappropriately narrow explicitly-specified throw signature) from happening:

1) First of all, you need to declare a public API. If it isn’t public, then there is no concern at all, you can evolve the implementation and clients together.

2) The Second problem depends on the number of errors it can throw. If there is exactly one type of error, the most common way to handle it is by returning optional. If you have one obvious failure mode with a value, then you throw that value. The most common case is where you can throw more than one sort of error, and therefore have an enum to describe it.

3) Third, your enum needs to be declared fragile in order to allow clients to enumerate their cases specifically.

The third step (having to mark your enum fragile, however it is spelled) is the biggest sign that you’re opting into a commitment that you should think really hard about. If folks don’t know that this is a big API commitment, then we have bigger problems.

One thing you didn’t mention is that boxing thrown values in an existential requires allocation in the general case. This may be unacceptable for some classes of Swift application (in the embedded / deep systems space) or simply undesirable because of the performance implication.

So, the performance implication cuts both ways. We can design the ABI for typed-throws so that, say, the callee initializes some buffer that's passed into it. That's an ABI that will kill some potential allocations in deep systems code, no question about it.

Agreed.

But in non-deep-systems code, we generally expect that error types will be resilient, which means that there are non-zero dynamic costs for allocating space on the stack for the error.

Proposed solution: ABI is that the callee takes in a register which is either a buffer address to fill in or null. On error, the callee returns the error pointer in a specific register. If there was a buffer passed in, it uses it, otherwise it allocates.

In practice, this allows the compiler to only pre-allocate the buffer when it knows the fixed size, otherwise the caller allocates on the heap on demand.

AFAICT, the cost of this API is only a “li rN, 0” in the normal path.

-Chris

···

On Feb 20, 2017, at 11:12 PM, John McCall <rjmccall@apple.com> wrote:

Sorry, I meant error_chain! handles the boilerplate. At the end of the day something has to create the new error types.

I agree with you on the chaining; if we really do want to go down this road we need an elegant solution. Seeing people talk about getting rid of rethrows makes me think this isn’t yet a serious discussion.

Russ

···

On Feb 21, 2017, at 8:18 AM, Joe Groff <jgroff@apple.com> wrote:

Yeah but Rust has a macro system so error-chain makes re-wrapping trivial. A simple `chain_err(|| “my new error message”)` is enough to define and “throw” a new error that wraps the underlying error.

That was my point—they recognized error chaining/layering as a common phonemenon and provided affordances for it as part of their design. I was saying we should do the same if we investigate typed errors seriously. (Not sure why you mentioned macros—chain_err is just a regular method, and could be defined as an extension method on Error in swift today.)

-Joe

When you're going to present a user with specific error message you don't do it for all errors. First of all at the end of a call chain you will have a couple of dozens of possible errors and only 2 or 3 will do really matter for the end user. Would you list all the dozen in params? Probably not. You'll just catch what matters and put the rest to the log with a default "sorry" message.

What I mean is it's OK to be "unaware" why everything failed. What really does matter is what you can deal with and what you can't (the rest). "The rest" is usually a magnitude bigger, though.

I might agree, that listing some errors in the throws clause is useful feature for documentation (even so, I think the docs should be in the comments), but only and only if it can throw the listed errors + a bunch of other stuff. In Java it's resolved with a terrible solution of RuntimeError interface which I definitely suggest to avoid.

As a library developer I can say that even for a library it's a nightmare and libraries will start wrapping system errors with their own if the trend would become to list errors in middleware. My preference though is to have all errors transparently passed in both roles, being a library developer and a library user.

Whether to wrap or not is a judgment call.

If the purpose of your library is to simply provide a cleaner experience for a system API that is a stable dependency that will not change. Users might expect to have the errors from the system API forwarded directly and that would be perfectly appropriate.

In other cases you might have dependencies that are considered implementation details. In that case if you don’t wrap the errors you’re doing users a disservice by giving them an unstable interface for error handling. Changing out your dependency that is supposed to be an implementation detail becomes a breaking change.

···

On Feb 28, 2017, at 5:09 AM, Daniel Leping <daniel@crossroadlabs.xyz> wrote:

On Tue, 28 Feb 2017 at 8:29 Karl Wagner <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

On 28 Feb 2017, at 02:43, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent from my iPad

On Feb 27, 2017, at 7:19 PM, Daniel Leping <daniel@crossroadlabs.xyz <mailto:daniel@crossroadlabs.xyz>> wrote:

Well, as Dave pointed, you can very rarely recover from an error, which IMO is absolutely true.

If your operation fails you don't really care unless you can recover. And you know your cases, which you can recover from (in reality one usually does it in optimization phase, though).

What else do you need the type of error for at the very end of your call stack? In 90% you will tell the user "ah, sorry, something happened. Come back later" and log the error (if you haven't forgot it).

In most cases the errors are not for recovering. They neither are to be presented to users. They are for developers to read the log/crash report/whatever else and analyze it. Most of the errors are for debugging purposes.

I don't want to deal with cumbersome code the purpose of which is to just "obey the language rules". Unless I know how to recover I would rethrow it. Than catch at the top of the stack and log + show the user a nice "sorry" message without getting techy.

In order to provide a helpful experience to end users an app needs to know about what might have caused the error and therefore what might (or might not) resolve it, allowing the operation to succeed on a subsequent attempt. This can influence the strategy an app uses to avoid bothering the user if it might be resolvable without user intervention and can also influence the content of the message presented to users if that is necessary.

Errors are certainly useful, but the key distinction (IMO) is that callers should never rely on an error happening or not. They are typically highly implementation and hence version-specific. I think that means specific errors shouldn’t be part of the function signature.

That doesn’t mean you can’t handle the error. A function that throws is one that reserves the right to fail; maybe you can resolve the problem, but maybe you can’t. That means your enclosing operation also needs to be prepared to fail for reasons beyond _its_ control, and so on. At some point your entire UI-level sub-operation (like opening the file, downloading the data from the network) just fails, and you decide whether or not the user needs to be told of that and how to do so.

Indeed, many apps don't bother with this kind of detail and just treat all errors the same. Personally, I find that to be an unfortunate state of affairs both as a user and as a developer.

Types can be useful in conveying this kind of information and abstracting low level details that are not helpful at higher levels in the stack. Of course types are not the only way to convey this information. But my experience is that untyped errors often result in libraries with poor documentation of error cases and not enough consideration of the error handling experience of users of the library in general. That makes it very difficult to handle errors well. I have experiences like this in several languages and on several platforms, including Apple's.

I’m not sure that types are really the best abstraction for “the list of errors that can be thrown by this function”. They’re fine for error themselves, but not for per-function error-lists IMO. Most of our types are way too rigid for this to be easy to live with.

If I understand what you’re saying, the core problem can be summarised as: "I don’t know/can’t easily communicate which errors this function throws”. Really, it is a documentation problem; it's too onerous to document every individual throwing function, even with our very pretty markdown-like syntax. I’m just not sure that the relatively rigid type-system is the solution.

Now, if we look at this from a documentation perspective: obviously, the compiler _typically_ can't generate documentation for you, because it doesn’t know what your types/functions are meant for. Error documentation, though, is different: the compiler *can* often know the specific errors which get thrown (or rethrown from a call to another function from whom it can get that information); and even when it can’t know that, often it can at least know the specific type of error. We could enhance the compiler libraries to track that; I spent half a day modifying the existing error-checker in Sema to prove the concept and it’s close.

Swift libraries don’t have header files, so third-parties will only ever see your documentation as generated by some tool which integrates the compiler. That means the compiler can “improve” the documentation when it is lacking. For yourself, if you care about explicitly writing out the errors which get thrown and you’re not working with an IDE, you could enable compiler-checking of your documentation comments. We can invent new shorthands to make it easier to list all of your various errors (individual cases, entire types, and maybe some private “other errors”), isolated from the rest of the language. For example:

/// - throws: MyError.{errorOne, errorThree}, AnotherError.*, *
func doSomething() throws

Maybe you can think of other ways we could help the documentation problem from the compiler side?

Typed errors are certainly no panacea but I believe they can be an important tool. Experience in other languages has shown that to be the case for many people writing many kinds of software.

If you don't believe typed errors will improve your code or if you just don't want to deal with typed errors, just don't use them! You will be able to continue using untyped throws just as you do today. You can even do this if you use libraries that throw typed errors.

If it’s the wrong solution to the problem, it does make the language worse overall.

On Tue, 28 Feb 2017 at 1:12 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> On Feb 27, 2017, at 5:01 PM, Dave Abrahams <dabrahams@apple.com <mailto: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 4:20 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:
>>>
>>>
>>> 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.
>
> Unless the caller casts away const, thus erasing information that was
> previously encoded in the type system.

Yes, of course.

>
>> 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.
>
> Unless the caller can recover (which is *very* rare) or it catches and
> rethrows one of the errors *it* declares, thus erasing information that
> was previously encoded in the type system.

I view this as being fundamentally different than casting away const. Casting away const says “I know better than the types”.

Converting an error to a different type is extremely different. It much more similar to any other kind of value wrapper a library might create in order to shield its users from being coupled to its implementation details / dependencies. This is not a way *around* the type system in the sense that casting away const is. It is a way of *using* the type system (hopefully) to your advantage.

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

_______________________________________________
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

IMHO, there are two kinds of responses to errors - a specific response,
and a general one. Only the calling code knows how it will deal with
errors, so a “typed throws” is the function guessing possible calling
code behavior.

The problem is, that gives four possible combinations - two where the
function guesses correctly, and two where it doesn’t. The most damaging
is when it suspects a caller doesn’t care about the error, when the
caller actually does. This is unwanted wrapping.

To provide an example, imagine a library that parses JSON. It has several
errors indicating JSON syntactic errors, and an “other” for representing
errors on the input stream. It wraps the input stream errors so that it
can provide a closed set of errors to the caller.

The caller is responsible for returning a data set. It doesn’t think that
code calling ‘it” cares about JSON syntactic errors, merely that the
object was not able to be restored. It returns its own wrapped error.

However, the original caller knows it is loading from disk. If the
problem is due to an issue such as access permissions, It has to know
implementation details of the API it called if it wishes to dive through
the wrapped errors to find out if the problem was filesystem related.

Add more layers, and it can be very mysterious why a call failed. Java at
least captures stack traces in this case to aid in technical support in
diagnosing the error.

Wrapping exceptions also prevents an aggregate of errors from different
subsystems being handled as a category, such as having a catch block
handle RecoverableError generically

Fwiw, I think wrapping errors is something that people are sometimes going
to want to do regardless of whether they are typed or not. Maybe the
solution is to better support wrapping errors by focusing on the problems
that wrapping causes. For example, we could do something like this to make

Just to clarify, do you think about something like this? :
(pseudocode, sorry for mistakes)

Is the question about the ability to create `fullErrorStackDescription`? Yes, that would certainly be possible to implement in an extension.

I’m not sure why your examples use `rethrow` to rethrow the error instead of `throw`. Was that a mistake?

I also want to reiterate that deciding when and how to wrap errors is something that requires careful thought and judgment. The goal is to provide a stable and ergonomic way for callers to learn about and handle errors they are likely to care about. There will often be an “everything else” case and that’s ok.

As others have pointed out, the important thing is that it is easy to identify cases where I can *improve* UX by recovery or specific messaging to the user. The fact that there will usually be a default path is a given, but it’s best to avoid that path if possible.

Well, I was trying to figure out how the some code could looks like with typed throws, taking into account your suggestion about built-in underlyingError and originalError props in Error protocol and the idea of "Maybe the solution is to better support wrapping errors by focusing on the problems that wrapping causes".
As for 'rethrow' it is not a mistake, but hypothetical use of the keyword to rethrow underlying error inside own error without boilerplate code, i.e. "better support wrapping", compare:

catch let e {
// need to somehow inject the current e instance into our error instance
// (or with some other syntax)
throw BarError.fooRelatedError(SomeType1(), underlying: e)
}

and

catch {
// 'rethrow' can clearly say that the current error instance will be injected
// into our own error instance in underlyingError prop. we focuse only
// on our own error instance
rethrow fooRelatedError(SomeType1())
}

Sorry for not clarifying all this in first message.

Ahh, ok. I think the best way to accomplish something like this would be with some kind of general error propagation, as has already been discussed. With a mechanism like that you wouldn’t have to catch the error at all when you’re just wrapping it.

···

On Feb 28, 2017, at 11:08 AM, Vladimir.S <svabox@gmail.com> wrote:
On 28.02.2017 19:48, Matthew Johnson wrote:

On Feb 28, 2017, at 6:47 AM, Vladimir.S <svabox@gmail.com> wrote:
On 28.02.2017 0:40, Matthew Johnson via swift-evolution wrote:

On Feb 27, 2017, at 1:46 PM, David Waite via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

func foo() throws {}

func bar() throws {}

enum BazError: Error { case baz1, case baz2 }
func baz() throws(BazError) {..}

enum BatError: Error {
case fooRelatedError(SomeType1)
case barOrBazRelatedError
case specialErrorOne(Int)
case specialErrorTwo(String)
}

func bat() throws(BatError) {
do {
  try foo()
}
catch {
  // underlyingError will be injected
  rethrow .fooRelatedError(SomeType1())
}

do {
  try bar()
  try baz()
}
catch {
  // underlyingError will be injected
  rethrow .barOrBazRelatedError
}

..
if flag1 { throw .specialErrorOne(intValue) }
..
if flag2 { throw .specialErrorTwo(stringValue) }
..
}

and then

func test() {
do {
  ...
  try bat()
  ...
}
catch let e as BatError {
  switch e {
    case fooRelatedError(let some) : { print(some, e.underlyingError) }
    case barOrBazRelatedError : {
    print("something with bar or baz, so try this:..")

    if let bazError = e.underlyingError as BazError {
      switch bazError {.....}
    } else {
      // do something about "bar" error
    }
  }
    case specialErrorOne(let i) : { print(i) }
    case specialErrorTwo(let s) : { print(s) }
  }

  log(e.fullErrorStackDescription) // BatError.description + underlyingError.description + underlyingError.underlyingError.description etc
}...
}

?

it easier to get at the original error:

protocol Error {
// The error directly underlying this error.
// Ideally the compiler would synthesize an implementation for enums
conforming to `Error`
// If `self` is a case that has an associate value which is or conforms
to `Error` that error would be returned, otherwise `nil` would be returned.
var underlyingError: Error? { get }

// The original error underlying *all* layers of wrapping.
// If underlyingError is non-nil this is also non-nil.
var originalError: Error { get }
}
extension Error {
  var underlyingError: Error? {
    return nil
  }
  var originalError: Error {
    return underlyingError?.originalError ?? underlyingError ?? self
  }
}

We could even provide syntactic sugar for catch sites that want to deal
with the original error rather than the wrapped error if that is an
important use case.

An interesting solution that has emerged in Ruby to keep library authors
from wrapping exceptions is by decorating the existing exception.
Exceptions are caught via pattern matching (same as in Swift), so rather
than wrap an extension, they extend the error instance with a
library-specific module (e.g. swift protocol). So while the error may be
a IOError in ruby, you can still catch it via ‘rescue JSONError’

Trying to specify the exact errors becomes even more destructive with
protocols and closures, where the person defining the interface knows
neither which errors the implementor of the call will throw, nor
necessarily if the caller will want to implement specific behavior on
those errors. This in my personal Java coding experience almost always
leads to wrapping in some protocol-specific Exception type which exposes
minimal information to the caller, or exposing your errors in some
unrelated type like IOException which was declared based on the author’s
experience of possible exceptions.

-DW

On Feb 27, 2017, at 5:19 AM, Daniel Leping via swift-evolution >>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

  on Fri Feb 17 2017, Joe Groff <swift-evolution@swift.org >>>>>> <mailto: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.

  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.

  --
  -Dave

  _______________________________________________
  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

_______________________________________________
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