> On Aug 18, 2017, at 11:09 AM, John McCall via swift-evolution 
> <[email protected]> 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
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to