Swift error handling suggestion


(Paweł Wojtkowiak) #1

I was writing some code yesterday which utilized throwing funcs and I found
some inconveniences which I think could be improved in some future version
of Swift.

1. *Default error handling*
Swift, unlike most languages, requires us to put try, try? or try! before
calling throwing funcs. When using try, we already have our do..catch block
surrounding the throwing func. Sometimes there are multiple cases where the
error is handled the same way, and there's a need to write the same
do..catch blocks every time, or just intercept the error in the throwing
func or a wrapping function for a throwing one, but then there is no
possibility to do the handling anymore if we want to handle it another way
in some specific case. I thought that this would be a good idea to
introduce something like default error handling e.g. with calling the func
without using the try keyword or do..catch block. In this case, a keyword
like throws? could be introduced to be used to use instead of throws when
defining a function. Such a function would:
1. Contain do..catch blocks, like normal throwing func
2. Would execute these do..catch blocks if the caller does not use a
variant of try when calling the func
3. Would not execute these do..catch blocks and pass the error to the
caller, the same way as the func would do if there would be no do..catch
blocks and the function was marked with "throws". In this case, the caller
would be the one to handle the error.

e.g.

enum SomeError: Error {
  case myError
}

func justThrowing() throws {
  throw SomeError.myError
}

func myFailableFunc() throws? {
  do {
    try justThrowing()
  } catch {
    print("Handled an error")
  }
}

func testing() {

  // This one would print "Handled an error"
  myFailableFunc()

  // This one would handle the error like any other func marked with
"throws"
  // try? and try! would also work exactly like for a normal throwing func
  do {
    try myFailableFunc()
  } catch {
    print("Handled this in the caller")
  }

}

2. *Grouping throwing function calls*

I think there are cases when people are not too concerned about an error
from multiple function calls in a row and what they want is to know if
these functions failed or not. This could be, for example, when these
operations are just doing some micro-work by calling the same func on
different instances of a type and are a part of some larger operation, it
is inconvenient to write try, try? or try! before each of them.

Instead, I would suggest introducing do try { ... } catch blocks or simply
allowing to use try like this:
try {
  throwingFunc1()
  throwingFunc2()
  throwingFunc3()
}

Now, we're forced to do it like this:
try throwingFunc1()
try throwingFunc2()
try throwingFunc3()

This often becomes inconvenient and looks messy in the code. I also think
that this concept could also work for try? and try!.

What are your opinions on these? Did you have any places in your code where
this could be useful?
If you have some better ideas on the concept or implementation, or have
some counterarguments to these suggestions, feel free to comment.


(Jon Shier) #2

Both of your proposals seem contrary to the very reasons Swift’s error handling is the way it is, so it’s unlikely they’ll find any support. I’m not aware of any “error handling” manifesto, as Swift 2 was created before the evolution process, but I’m sure someone has something to share about why Swift’s error system is designed this way.

Jon

···

On Mar 2, 2017, at 3:14 AM, Paweł Wojtkowiak via swift-evolution <swift-evolution@swift.org> wrote:

I was writing some code yesterday which utilized throwing funcs and I found some inconveniences which I think could be improved in some future version of Swift.

1. Default error handling
Swift, unlike most languages, requires us to put try, try? or try! before calling throwing funcs. When using try, we already have our do..catch block surrounding the throwing func. Sometimes there are multiple cases where the error is handled the same way, and there's a need to write the same do..catch blocks every time, or just intercept the error in the throwing func or a wrapping function for a throwing one, but then there is no possibility to do the handling anymore if we want to handle it another way in some specific case. I thought that this would be a good idea to introduce something like default error handling e.g. with calling the func without using the try keyword or do..catch block. In this case, a keyword like throws? could be introduced to be used to use instead of throws when defining a function. Such a function would:
1. Contain do..catch blocks, like normal throwing func
2. Would execute these do..catch blocks if the caller does not use a variant of try when calling the func
3. Would not execute these do..catch blocks and pass the error to the caller, the same way as the func would do if there would be no do..catch blocks and the function was marked with "throws". In this case, the caller would be the one to handle the error.

e.g.

enum SomeError: Error {
  case myError
}

func justThrowing() throws {
  throw SomeError.myError
}

func myFailableFunc() throws? {
  do {
    try justThrowing()
  } catch {
    print("Handled an error")
  }
}

func testing() {
  
  // This one would print "Handled an error"
  myFailableFunc()

  // This one would handle the error like any other func marked with "throws"
  // try? and try! would also work exactly like for a normal throwing func
  do {
    try myFailableFunc()
  } catch {
    print("Handled this in the caller")
  }

}

2. Grouping throwing function calls

I think there are cases when people are not too concerned about an error from multiple function calls in a row and what they want is to know if these functions failed or not. This could be, for example, when these operations are just doing some micro-work by calling the same func on different instances of a type and are a part of some larger operation, it is inconvenient to write try, try? or try! before each of them.

Instead, I would suggest introducing do try { ... } catch blocks or simply allowing to use try like this:
try {
  throwingFunc1()
  throwingFunc2()
  throwingFunc3()
}

Now, we're forced to do it like this:
try throwingFunc1()
try throwingFunc2()
try throwingFunc3()

This often becomes inconvenient and looks messy in the code. I also think that this concept could also work for try? and try!.

What are your opinions on these? Did you have any places in your code where this could be useful?
If you have some better ideas on the concept or implementation, or have some counterarguments to these suggestions, feel free to comment.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #3

The error handling rationale is in the compiler repo here:

https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst

-Joe

···

On Mar 2, 2017, at 12:39 PM, Jon Shier via swift-evolution <swift-evolution@swift.org> wrote:

Both of your proposals seem contrary to the very reasons Swift’s error handling is the way it is, so it’s unlikely they’ll find any support. I’m not aware of any “error handling” manifesto, as Swift 2 was created before the evolution process, but I’m sure someone has something to share about why Swift’s error system is designed this way.