Catch specific error type without discarding it

Consider the following enum and function signature:

public enum MyError: Swift.Error {
    case couldNotDecode(String)
    case decodingNotSupported(String)
    case notYetImplemented
    case other(Swift.Error)
}

static func tryDecode<T>(from json: Any) -> Result<T, MyError>

If I want to implement this function, I need to do something like this:

static func tryDecode<T>(from json: Any) -> Result<T, MyError> {
    do {
        let value: T = try ...
        return .success(value)
    } catch /* somehow catch a MyError */ {
        return .failure(error)
    } catch {
        return .failure(.other(error))
    }
}

I have tried a handful of syntaxes to replace /* somehow catch a MyError */ with something that I thought would do just that, but I don't think it's possible. catch is MyError completely discards the error (who thought that was a good idea?) and nothing else compiles, short of catching and re-packing the payload of each individual case.

Is there some poorly documented syntax that works? Or is this a gap in Swift's language features?

1 Like

In catch statements the rules of switch cases apply, so you can write the following:

do {
  ...
} catch let error as MyError {
  return .failure(error)
} catch {
  return .failure(.other(error))
}
3 Likes

Ah, I saw some weird syntax like catch(let exception) somewhere so I tried catch(let error: MyError) but not that. Thanks!

It always surprises me that catch as MyError doesn’t work.

We can write catch is MyError and that works, but then there’s no bound name for the error.

We can write catch MyError.foo, which also works but doesn’t bind a name.

But we can’t just write catch as MyError. I would have expected that to be the second most common case behind simply catch, and I would expect both to bind the default name error.

Instead we have to write out longhand catch let error as MyError.

5 Likes

I wouldn't dare go back and read the evolution threads, but I bet what happened is they decided it was better to make it explicit that you're declaring a variable or something?

Which doesn't make sense, because a regular catch by itself does declare a variable, and there's no let needed. At least be consistent!

:+1: Although in general we've been moving away from magic bindings, given that we'll have to support catch indefinitely anyway, and catch is MyError works, I agree that it's quite reasonable to support catch as MyError and that it's more surprising than not that it doesn't.

4 Likes

I wouldn't dare guess the definition of consistency, but catch-pattern is just a regular pattern (those you'd find on switch-case). So you can use a fair number of things (not all, mind you) found there:

enum Err: Error {
    case a, b(Int)
}

do {
} catch Err.a {
} catch Err.b(let value) {
} catch let x {
} catch is Err {
} catch _ {
}
2 Likes

That's fine, the problem is it discards the error variable. catch is MyError was the first thing I tried :/

1 Like