3

I'm trying to do this:

 catch LocksmithError.Duplicate, LocksmithError.Allocate {...}

But I get an error at , saying :

Expected '{' after 'catch' pattern

Does this mean you can't combine cases like case expression2, expression3 :? Any reason it's made this way?

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 1
    Need to stop using capital letters for case names. :) That is sooooo 2015. – matt Oct 19 '16 at 15:49
  • 1
    Oh no! So we _are_ still in 2015. Bring back disco and telephones with cords, too. :))))) – matt Oct 19 '16 at 15:52

3 Answers3

4

No, it's currently not possible to combine multiple patterns in a catch clause – the grammar (as detailed by the Swift Language Guide) only allows for a single pattern to match against:

catch-clause → catch pattern­opt ­where-clause­opt ­code-block­

Another possible solution to the ones proposed already, as long as your error enum is trivial (which LocksmithError appears to be), is to use a where clause after a binding pattern:

catch let error as LocksmithError where error == .Duplicate || error == .Allocate {
    // ...
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Okay, this is the bast workaround, no question. I'm going to delete my answer! – matt Oct 19 '16 at 16:07
  • Is this intended or it's a feature that should be out soon? @matt – mfaani Oct 19 '16 at 16:09
  • @Honey I'm not sure if you're intending to address myself or matt – but I'm not aware of any Swift Evolution discussions around `catch` clauses supporting multiple patterns. As matt said in his (now deleted) answer, you can file a bug at http://bugs.swift.org/ if you wish, or maybe even better, bring it up on the Swift evolution mailing list. – Hamish Oct 19 '16 at 16:25
  • So you're saying there is no merit to the current way and it seems like a bug? I am insisting on this question so I can better understand Swift. – mfaani Oct 19 '16 at 16:28
  • 1
    I'm not entirely sure what you mean by "*no merit to the current way*" – but it's currently 'working as intended', as the grammar for a `catch` clause is well documented. Therefore it's not really a bug, just a missing feature (apologies, when I said 'file a bug' earlier, I really meant file an *improvement* (both happen through https://bugs.swift.org) – or, as I ninja edited in, you can always bring it up on the [Swift evolution mailing list](https://lists.swift.org/mailman/listinfo/swift-evolution)). – Hamish Oct 19 '16 at 16:40
2

Given that you let your LocksmithError have some rawvalue type (e.g. Int), you could, in a single catch statement, bind the error thrown and use its rawValue to test for inclusion into one of several error cases (using the where clause after binding). E.g.:

enum FooError: Int, Error {
    case err1 = 1, err2, err3, err4, err5
}

func foo(_ bar: Int) throws {
    if let err = FooError(rawValue: bar) { throw err }
}

func tryFoo(_ bar: Int) {
    do {
        try foo(bar)
    } catch let err as FooError where (1...4).contains(err.rawValue) {
        print("Any of 1st through 4th error!")
    } catch FooError.err5 {
        print("5th error!")
    } catch {}
}

tryFoo(1) // Any of 1st through 4th error!
tryFoo(4) // Any of 1st through 4th error!
tryFoo(5) // 5th error!

As suggested by @user28434 (thanks!), there's really no need to apply the rawValue restraint as the same method above could be used to direcly see if the binded err is a member of an array of given cases.

enum FooError: Error {
    case err1, err2, err3
}

func foo(_ bar: Int) throws {
    guard bar != 1 else { throw FooError.err1 }
    guard bar != 2 else { throw FooError.err2 }
    guard bar != 3 else { throw FooError.err3 }
}

func tryFoo(_ bar: Int) {
    do {
        try foo(bar)
    } catch let err as FooError where [.err1, .err2].contains(err) {
        print("1st or 2nd error!")
    } catch FooError.err3 {
        print("3rd error!")
    } catch {}
}

tryFoo(1) // 1st or 2nd error!
tryFoo(2) // 1st or 2nd error!
tryFoo(3) // 3rd error!

This reduces to basically just a variation of the accepted answer (possibly useful for catch blocks covering more than just two cases, but in such cases, possibly the error enum should consider refactoring).

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • So the answer is no. But there is a workaround as you mentioned. Any reason as for why? Is it because errors should have a one to one functionality? – mfaani Oct 19 '16 at 15:46
  • @Honey I could speculate that it's a feature that is not high on a possibly prio list as we can refine the pattern matching within the actual `catch` block, as shown in the other answer. – dfrib Oct 19 '16 at 15:58
  • @downvoter: please consider supplying feedback as to why you believe this is an outright bad answer, I might learn something! – dfrib Oct 19 '16 at 15:59
  • @dfri, there **no** need in `Int` rawType, `catch let err as FooError where (1...2).contains(err.rawValue)` can be rewritten as `catch let err as FooError where [.err1, .err2].contains(err)`. Look, mom, no raw type. – user28434'mstep Oct 19 '16 at 16:03
  • 1
    @user28434 that's a good proposal, thanks (although modifing my answer into the one you supply is basically @Hamish's solution). Using `rawValue` matching could possibly be useful in some horrible corner case with lots of different error cases (in which case the error enum should probably be refactored ...). No need to be complacent, though :) – dfrib Oct 19 '16 at 16:08
  • 1
    I like the use of the array, that'll indeed be much slicker for more error cases :) – Hamish Oct 19 '16 at 16:20
  • @dfri, removed downvote, and to be honest i just dislike this synctactic sugar with matching error enum cases right in the catch statement at all. – user28434'mstep Oct 19 '16 at 16:22
  • @user28434 I agree, in many cases we'd also like to bind the contained properties (e.g. `err4(int)`) of each error case for detailed processing, in which case we'll have to resort to the `switch` block anyway. But on another note, you'll probably run into many Q&A:s here on SO where answers give insight into specific technical questions which, by question design (e.g. _"is this < concept ... > possible"_), will yield techn. solutions that are not preferable by all (semantically/prefer. practice). Hopefully, though, some readers (other than OP) will find these Q&As useful (see e.g. C++ tag.. x) – dfrib Oct 19 '16 at 16:29
1

It's just a syntactical problem, it may be improved in Swift 4+.

Right now you can use this:

catch let locksmithError as LocksmithError {
    switch locksmithError {
        case .Duplicate, .Allocate:
            // your code
        …
    }
}
user28434'mstep
  • 6,290
  • 2
  • 20
  • 35
  • 1
    @Honey, Yeah. But I just showed a way how to handle few cases of enum-error with same action(?). So yeah you may need to add some extra catches, some extra cases in switch, etc. – user28434'mstep Oct 19 '16 at 16:00
  • **comment updated**: As mentioned [here](http://stackoverflow.com/questions/30826560/swift-2-invalid-conversion-from-throwing-function-of-type-to-non-throwing-funct). You always need to have a default catch so in case the error thrown is something *other than* LocksmithError type you would STILL be able to catch it. For more info see the comments for [this](http://stackoverflow.com/a/40184140/5175709) answer – mfaani Oct 21 '16 at 23:54