Rethrow inside the catch block

Hello all.

Sometimes I have code like this in my apps:

do {
    try self.requestSomething()
}
catch RequestError.failed(let statusCode) {
    if statusCode == 404 {
        self.doSomethingElse()
    }
    else {
        throw RequestError.failed(statusCode)
    }
}

What I'd like to write is:

do {
    try self.requestSomething()
}
catch RequestError.failed(let statusCode) {
    if statusCode == 404 {
        self.doSomethingElse()
    }
    else {
        rethrow
    }
}

In C# there is the empty throw (wich many programmers forget about and use throw error losing the real exception callstack).

I know that in Swift the context is not the same, but I'm thinking about something like that. A keyword called rethrow (to match the other pair: throws and rethrows) that simply rethrows the error captured in the catch statement.

(If Swift already has equivalent functionally, please enlighten me.)

What do you think?

— Van

2 Likes

Important thing to understand: Swift has NO exceptions. Do catch is only syntactic sugar for the return of a NSError value.

Well remembered. That's why I said that the context is not the same. By the way, Swift's solution is very shrewd. :slight_smile:

I know this doesn't handle all possible cases where rethrow might be used, but in your given example you can write this in current Swift:


do {
    try self.requestSomething()
} catch RequestError.failed(statusCode: 404) {
    self.doSomethingElse()
} catch let error {
    throw error
}

... which is an incredibly elegant solution to the problem. You can even drop the 'let error' from the final catch statement if you want even more brevity. I find most error-handling situations in my own code can be dealt with in similar ways to this.

2 Likes

@bzamayo Good catch! (No pun intended.) I think that in your example the catch let error can be omitted at all, leaving just the catch RequestError.failed(statusCode: 404). Am I right?

1 Like

Not quite. The Swift error pattern matching model has a weird quirk where it binds a variable error in the catch all case where you specify no pattern. So this is valid code:

do {
    try self.requestSomething()
} catch RequestError.failed(statusCode: 404) {
    self.doSomethingElse()
} catch {
    throw error
}

Of course, if the outer context is throwing, then yeah — you could omit the final catch too.

In Swift, you can just throw the error value you caught, perhaps using the implicit error variable binding inside catch blocks:

do {
  try something()
} catch {
  doMyErrorHandlingStuff()
  throw error
}

Unlike C++, there'd be no semantic difference between a rethrow and throwing the same value, so there's no need for rethrow to be a special form.

Here's one of my use cases:

    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
            case .keyNotFound:
                throw error
            }
        }
    }

What I really wanted to write was this:

do {
  // stuff
} catch DecodingError.dataCorrupted, DecodingError.typeMismatch, DecodingError.valueNotFound {
  decoded = nil
}

But the catch clause's pattern matching abilities don't seem to be as fully featured as switch.

1 Like

Hm, I can't think of a good reason we wouldn't support multiple patterns in a catch block. That seems worthy of a bug report to me.

4 Likes

https://bugs.swift.org/browse/SR-8253

3 Likes

Thanks!