[swift-evolution] typed throws

Joe Groff jgroff at apple.com
Fri Aug 18 13:41:24 CDT 2017


> On Aug 18, 2017, at 11:09 AM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> I think you're right that wrapping errors is tightly related to an effective use of typed errors.  You can do a reasonable job without language support (as has been discussed on the list in the past).  On the other hand, if we're going to introduce typed errors we should do it in a way that *encourages* effective use of them. My opinion is that encouraging effect use means categorizing (wrapping) errors without requiring any additional syntax beyond the simple `try` used by untyped errors.  In practice, this means we should not need to catch and rethrow an error if all we want to do is categorize it.  Rust provides good prior art in this area.
> 
> Yes, the ability to translate errors between domains is definitely something we could work on, whether we have typed errors or not.

The Rust approach of automatically wrapping errors when you "cross domains", so to speak, has the disadvantage you've observed before that the layers of wrapping can obscure the structure of the underlying error when you're trying to ferret out and handle a particular form of failure mode. An alternative approach that embraces the open nature of errors could be to represent domains as independent protocols, and extend the error types that are relevant to that domain to conform to the protocol. That way, you don't obscure the structure of the underlying error value with wrappers. If you expect to exhaustively handle all errors in a domain, well, you'd almost certainly going to need to have a fallback case in your wrapper type for miscellaneous errors, but you could represent that instead without wrapping via a catch-all, and as?-casting to your domain protocol with a ??-default for errors that don't conform to the protocol. For example, instead of attempting something like thi
 s:

enum DatabaseError {
  case queryError(QueryError)
  case ioError(IOError)
  case other(Error)

  var errorKind: String {
    switch self {
      case .queryError(let q): return "query error: \(q.query)"
      case .ioError(let i): return "io error: \(i.filename)"
      case .other(let e): return "\(e)"
    }
  }
}

func queryDatabase(_ query: String) throws /*DatabaseError*/ -> Table

do {
  queryDatabase("delete * from users")
} catch let d as DatabaseError {
  os_log(d.errorKind)
} catch {
  fatalError("unexpected non-database error")
}

You could do this:

protocol DatabaseError {
  var errorKind: String { get }
}

extension QueryError: DatabaseError {
  var errorKind: String { return "query error: \(q.query)" }
}
extension IOError: DatabaseError {
  var errorKind: String ( return "io error: \(i.filename)" }
}

extension Error {
  var databaseErrorKind: String {
    return (error as? DatabaseError)?.errorKind ?? "unexpected non-database error"
  }
}

func queryDatabase(_ query: String) throws -> Table

do {
  queryDatabase("delete * from users")
} catch {
  os_log(error.databaseErrorKind)
}

-Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170818/ff5e65be/attachment.html>


More information about the swift-evolution mailing list