Allow multiple patterns in catch clauses

It's possible to handle multiple kinds of errors by using multiple catch clauses. However, there are cases where this results in unnecessarily repeated code, because it's not easy to share the same code across multiple catch clauses. One thing you can do with a switch statement is to have a case match multiple patterns by separating them with commas, so I expected this code to compile:

struct Safe<T: Decodable>: Decodable {
  let decoded: T?

  init(from decoder: Decoder) throws {
    do {
      decoded = try decoder.singleValueContainer().decode(T.self)
    } catch DecodingError.dataCorrupted,
        DecodingError.typeMismatch,
        DecodingError.valueNotFound {
      decoded = nil
    }
  }
}

But unfortunately this isn't possible with catch clauses today. It can be rewritten like so:

struct Safe<T: Decodable>: Decodable {
  let decoded: T?

  init(from decoder: Decoder) throws {
    do {
      decoded = try decoder.singleValueContainer().decode(T.self)
    } catch let error as DecodingError {
      switch error {
      case .dataCorrupted, .typeMismatch, .valueNotFound:
        decoded = nil
      default:
        throw error
      }
    }
  }
}

I think it makes a lot of sense for catch clauses to be improved by allowing multiple patterns. I initially mentioned it in this thread: Rethrow inside the catch block. I was encouraged by Joe Groff to file a bug, which I did: [SR-8253] Catch clause should allow multiple patterns · Issue #50785 · apple/swift · GitHub.

John McCall pointed out that this is a language change that needs to go through the evolution process, hence this thread.

Would this be a good addition to Swift?

8 Likes

I neutral on its inclusion, but this would make a really good starter bug if you or anybody else wants to take it up. I'd love to give somebody a hand with this even if it winds up being a purely academic exercise.

1 Like

It might be interesting to consider this together with the idea of always binding error to the caught value, which would make it straightforward to e.g. rethrow the original error or attach it to something else.

3 Likes

Is this kind of thing what you had in mind?

do {
  try something()
} catch DecodingError.dataCorrupted {
  // error is DecodingError
}

I would definitely be interested in giving this a shot! (Although I don't have a huge amount of time to work on it.)

I guess the next step is probably to put together a draft proposal, while hacking on an implementation?

Yeah, that's exactly what I was thinking. There are arguments against it, of course.

Would that still work with patterns of different types?

I was thinking that it would always be of type Error.

Right, I suppose that would be consistent with this equivalent switch statement:

switch error {
case FooError.foo, BarError.bar:
  // do something with error
}