Pattern matching and binding simultaneously?

Pattern matching lets you either match a pattern or bind a value, but not at the same time, as far as I can tell.

An example:

enum Response {
    case success(MyStruct)
    case failure(Error)
}

enum MyError: Error {
    case badResponse(code: Int)
}

switch response {
    case .success(let value):
        // Do something with value.
    case .failure(MyError.badResponse(code: 404)):
        // Works fine, signal failure handler or something here. But we don’t bind it to a value for further processing.
    case .failure(let error):
        // Works fine, but binds any kind of error.
    case .failure(let error as MyError):
        // Works fine, but binds any case of MyError.
    case .failure(let error as MyError) where error == .badResponse(code: 404):
        // Works, but requires MyError to conform to `Equatable`.
    case .failure(let error = MyError.badResponse(code: 404)):
        // This is what I want; match a specific case of error, while binding it to the value `error` so that it can be forwarded to some other error handler or reporter.
}

I think I have exhausted the alternatives in this switch, but it feels like a hole in the language that you have powerful pattern matching and you have the ability to bind values, but not at the same time with this kind of granularity.

Can't you pass literally MyError.badResponse(code: 404) for further processing, and if not is this because you do not want to repeat that expression twice or is there another reason?

Yes, it is the repetition I want to avoid.

A couple of workarounds you can do:

  1. Conform the enum to Equatable.

  2. Perform a cast:

case .failure(MyError.badResponse(code: 404)):
  handleMyError(error as! MyError)
  1. Add this case to your MyError enum: case other(Error), then:
let mappedResponse = response.mapFailure { ($0 as? MyError) ?? MyError.other(error) }
switch mappedResponse {
  case .failure(MyError.badResponse(code: 404)):
    handleMyError(error) // No cast needed as error is already MyError
}

Maybe you can try matching specific error property?

case .failure(let error as MyError) where error.code == 404:

You can’t really do that since MyError doesn’t have a code property.

You can add one:

extension MyError {
  var code: Int { 
    switch self {
      case let .badResponse(responseCode):
        return responseCode
    }
  }
}

I think this would be nicer than other alternatives.

That sounds as doable as conforming to Equatable, and still won’t let you match on a specific case of MyError, just on a code.

More and more, this feels like a hole in the language.

If you just want to match on .badResponse, regardless of the code, you can do something like:

extension MyError {
  var isBadResponse: Bool {
    switch self {
      case .badResponse:
        return true
      default:
        return false
    }
  }
}

Then in your switch you can do:

switch response {
  case let .failure(error as MyError) where error.isBadResponse: 
    handleBadResponse(error)
}

And yes, many people have pitched ideas here for better pattern matching when using enums.

That would also entail as much work as implementing Equatable when all I want to do is match and bind, both capabilities which are otherwise part of any switch or case statement.

Agree with others that your type semantically fulfills the requirements of Equatable and could just conform. Not sure I understand why the reticence to do so.

That said, you can just switch over (response, response), binding one and pattern matching the other. Or nest if case inside the case where you match on the type.

Depending on the number of cases and associated values, implementing Equatable can be quite arduous.

Switching on (response, response) might actually be the easier way out there, but it looks rather silly to do that when the necessary value is already sitting there in front of you, waiting to be bound.