SE-0276: Multi-Pattern Catch Clauses

The review of SE-0276 — Multi-Pattern Catch Clauses begins now and runs through January 22, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Doug Gregor
Review Manager

16 Likes
  • What is your evaluation of the proposal?
    +1
  • Is the problem being addressed significant enough to warrant a change to Swift?
    Yes
  • Does this proposal fit well with the feel and direction of Swift?
    Yes. I am often reminded that this isn't currently a feature when I try to use it.
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    I read the proposal and have followed various conversations when it was brought up.

+1. This seems like an obviously correct and uncontroversial thing to do.

Yes and yes. I think many of us have been bitten by this inconsistency; fixing it unifies error patterns with other pattern matching in the language.

I haven't used any languages that allow pattern matching in error clauses to the degree that Swift does.

Briefly tracked the original discussion and read the proposal.

2 Likes
  • What is your evaluation of the proposal?
    +1

  • Is the problem being addressed significant enough to warrant a change to Swift?
    Not really sure. I have not felt substantial pain around the use of patterns in catch statements but I can vaguely recall a few times I would have used such a capability in the past.

  • Does this proposal fit well with the feel and direction of Swift?
    Yes

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    Nothing comes to mind.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    A quick read of the proposal.

What is your evaluation of the proposal?

+1

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, I find myself needing multi-pattern catches from time to time.

Does this proposal fit well with the feel and direction of Swift?

Yes, it makes the pattern matching rules more universal.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal.

On a quick skim, this makes sense to me. It seems like a straightforward extension of current behavior.

(In hindsight, I kind of wish we’d designed catch differently:

do { ... }
catch {
case TaskError.someRecoverableError:
  recover()
case TaskError.someFailure(let msg),
     TaskError.anotherFailure(let msg):
  showMessage(msg)
// could have a default case, but since we don’t,
// we get one that automatically rethrows
}

But that ship has long since sailed. Oh well, just another entry in REGRETS.md.)

18 Likes
  • What is your evaluation of the proposal?

I think it fixes an obvious drawback of do-catch, so +1.

  • Is the problem being addressed significant enough to warrant a change to Swift?

I think so. Personally I haven't had a need to use this pattern of error handling (there are other ways, such as using Result, etc), but I think it would be a useful addition to the language and can help simplify code in certain scenarios (as mentioned in the proposal).

  • Does this proposal fit well with the feel and direction of Swift?

Absolutely.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I have used similar functionality in C# (exception filters) long time ago and I think this is comparable to that.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the pitch thread, proposal and briefly looked at the implementation code.

macOS (Download Link) and Linux (Download Link) toolchains are now available with the implementation of this feature. If using it with Xcode, I recommend quitting and restarting the app after switching toolchains, otherwise it seems to continue using the older SourceKit for live error messages.

What is your evaluation of the proposal?

+1.

Is the problem being addressed significant enough to warrant a change to Swift?

It's not critical and won't change my life, but it's a nice to have and will no doubt be one of those niceties that I would occasionally use.

Does this proposal fit well with the feel and direction of Swift?

Yes, it fits and almost feels a bit silly that it wasn't there already.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal.

2 Likes

+1, this looks like an obvious addition to me.

This is some low hanging fruit for me and will be more than welcome. Thanks!

I have two cases where is not clear for me how this change fits the existing idioms:

Ex 1

do {
  try performTask()
} catch TaskError.someRecoverableError {
  recover()
} catch TaskError.someFailure,    // Match the exception but didn't bind msg
        TaskError.anotherFailure(let msg) {
  showMessage(msg)
}

What happen here when the catch match with TaskError.anotherFailure? Is this a compilation error?

Ex 2

do {
  try performTask()
} catch TaskError.someRecoverableError {
  recover()
} catch TaskError.someFailure(let msg) as excObject,    // Bind excObject to the exception 
        TaskError.anotherFailure(let msg) {
  print(excObject)
  showMessage(msg)
}

Similar to the previous case. What's the behavior here if the catch match with TaskError.anotherFailure?

The relevant part of the proposal is:

Similar to switch cases, catch clauses with multiple patterns may still contain value bindings. However, those bindings must have the same name and type in each pattern.

As a result, your first example results in a compiler error: error: 'msg' must be bound in every pattern.

Your second example is also invalid code because as is used to write a type-casting pattern, and the right-hand-side, excObject, is not a type.

1 Like

Just out of curiosity, why wouldn‘t we able to introduce a new syntax form such as you mentioned and slowly fade or not further extend the older one?

If we‘d introduce a switch like catch block then people will start using it and slowly over time the old version will likely go away as all new switch like features would be supported in that new form.

And one more thing, wouldn‘t your catch (switch like) block require a default case at the end?

3 Likes

For example wouldn‘t a catch block also benefit from this ability as well (assuming it gets accepted and implemented)?

do {
  ...
} catch {
case where <condition> in <pattern_1>, <pattern_2>:
  ...
}

To wrap up, I would support this proposal as it fits well as an extension of the current functionality. However after reading @beccadax‘s example, I‘m intrigued by this more general syntax form which already would benefit from all switch functionalities that we have today or in the future. So why trying to align something as closely as possible to switches rather than reusing the whole switch case mechanism for do catch?

If there is a chance to go the other way now, I‘d rather choose it and vote my -1 on the current proposed solution.


Under the assumption that we eventually get typed throws, a do catch case would automatically allow us to drop the type prefix from all the patterns. I‘m not sure if this would be the case for the current do catch though, this also depends on how many different try's are in the do block.

2 Likes

What is your evaluation of the proposal?
+1

Is the problem being addressed significant enough to warrant a change to Swift?
Yes

Does this proposal fit well with the feel and direction of Swift?
Yes

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Read the proposal.

Getting off topic, but the problem with changing catch to use case is that, when I imagine the proposal, it includes this text:

Motivation

It implies that catch supports all case patterns. Also, we like it better.

It's a little late to make those kinds of changes to the language when there are much less disruptive alternatives. Don't go -1 on a hypothetical.

(Besides, the pre-open-source Swift team were a pretty clever bunch; they may have thought about this design and rejected it.)

3 Likes

+1

+1

I find Switch's pattern matching syntax to be more intuitive and easier to work with so am all for catch to be used in a similar manor.

One question, with the example...

catch TaskError.someRecoverableError {    // OK
  recover()
} 

Is there still an implicit error parameter? Is there any way to access the original error at all and/or rename it?

Basically...

catch TaskError.someRecoverableError { 
    print(error.localizedDescription) // Is this possible?
    // Also what Type would error be? `Error` or `TaskError`?
} 

No, this proposal doesn’t change the implicit error binding behavior, which remains only supported in empty catch clauses. This is addressed briefly under alternatives considered. Basically, there are still some open design questions and minor source compatibility issues which need to be resolved before extending the implicit error binding. For example, it might be useful to first introduce the notion of conjunctive patterns like let error as Error @ TaskError.someError, to borrow some syntax from Rust. The error binding could then just be syntactic sugar so users wouldn’t need to write the first part in the common case. Any changes in this area deserve their own dedicated proposal though.