I'm trying to write a custom pattern matching that lets me switch on an error and match against the error code. See an example below:
enum ErrorCode: Int {
case notSoDumb
case dumbError
}
let myError = NSError(domain: "My domain", code: ErrorCode.dumbError.rawValue, userInfo: nil)
func ~=(pattern: ErrorCode, value: NSError) -> Bool {
return (ErrorCode(rawValue: value.code) == pattern)
}
switch myError {
case ErrorCode.notSoDumb:
print("Not a dumb error")
case ErrorCode.dumbError:
print("Super dumb error")
default:
print("No matches!")
}
The first case in my switch statement has an error of Enum case 'notSoDumb' is not a member of type 'NSError'
. If I replace the ErrorCode
enum with integers (and update my custom ~=
operator to match Int
s and NSError
s, everything works fine.
This is a known bug with enum
cases in pattern matching; the compiler incorrectly assumes that it's always working with an enumeration case pattern rather than an expression pattern.
Until fixed, one way to force the compiler into 'expression pattern mode' is to first bind the case a temporary:
let notSoDumbErrorCode = ErrorCode.notSoDumb
let dumbErrorCode = ErrorCode.dumbError
switch myError {
case notSoDumbErrorCode:
print("Not a dumb error")
case dumbErrorCode:
print("Super dumb error")
default:
print("No matches!")
}
However this is pretty clunky. A perhaps better workaround would be to use a struct
with static members rather than an enum
:
import Foundation
struct ErrorCode : Equatable, RawRepresentable {
let rawValue: Int
static let notSoDumb = ErrorCode(rawValue: 0)
static let dumbError = ErrorCode(rawValue: 1)
}
let myError = NSError(domain: "My domain",
code: ErrorCode.dumbError.rawValue,
userInfo: nil)
func ~=(pattern: ErrorCode, value: NSError) -> Bool {
return value.code == pattern.rawValue
}
switch myError {
case ErrorCode.notSoDumb:
print("Not a dumb error")
case ErrorCode.dumbError:
print("Super dumb error")
default:
print("No matches!")
}
This also enables you to add additional error codes via extensions later down the line (it behaves more like an open enum). Although it does remove validation from init(rawValue:)
, which may or may not be desirable (you could always implement your own init?(rawValue:)
though).
Or, as you say in your comment, you could stick with using an enum
, but instead use an intermediate function call when pattern matching to force the compiler into 'expression pattern mode':
enum ErrorCode : Int {
case notSoDumb
case dumbError
}
// ...
func identity<T>(_ t: T) -> T { return t }
switch myError {
case identity(ErrorCode.notSoDumb):
print("Not a dumb error")
case identity(ErrorCode.dumbError):
print("Super dumb error")
default:
print("No matches!")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With