92

Language: Swift2.3

For example let's I'll show you different kinds of enums

enum Normal {
    case one
    case two, three
}

enum NormalRaw: Int {
    case one
    case two, three
}

enum NormalArg {
    case one(Int)
    case two, three
}   

Switch can be used on all three enums like so:

var normal: Normal = .one
var normalRaw: NormalRaw = .one
var normalArg: NormalArg = .one(1)

switch normal {
    case .one: print("1")
    default: break
}

switch normalRaw {
    case .one: print(normalRaw.rawValue)
    default: break
}

switch normalArg {
    case .one(let value): print(value)
    default: break
}

On the if-else statement though I can only do comparison for Normal and NormalRaw, and an error message shows for NormalArg, so I can't run the code

Binary Operator '==' cannot be applied to operands of type NormalArg and _

Here's the code example:

if normal == .two { // no issue
    .. do something
}

if normalRaw == .two { // no issue
    .. do something
}

if normalArg == .two { // error here (the above message)
    .. do something
}

if normalArg == .one(_) { // error here (the above message)
    .. do something
}

if normalArg == .three { // error here (the above message)
    .. do something
}

Any Ideas? I'm not really doing anything with this code, I'm just wondering as to why we can't do comparison.

Zonily Jame
  • 5,053
  • 3
  • 30
  • 56

2 Answers2

208

The trick is to not actually check with == but rather use the case keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let, you get used to it pretty fast:

enum Normal {
    case one
    case two, three
}

enum NormalRaw: Int {
    case one = 1
    case two, three
}

enum NormalArg {
    case one(Int)
    case two, three
}


let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)

if case .one = normalOne {
    print("A normal one") //prints "A normal one"
}

if case .one = normalRawOne {
    print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}

if case .one(let value) = normalArgOne {
    print("A normal \(value)") //prints "A normal 1"
}

The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:

Normal.one == .one //true
Normal.one == .two //false

NormalRaw.one == .one //true
NormalRaw.one == .two //false

NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?

Maybe this makes it clearer why this cannot work out of the box:

class Special {
    var name: String?
    var special: Special?
}

enum SpecialEnum {
    case one(Special)
    case two
}

var special1 = Special()
special1.name = "Hello"

var special2 = Special()
special2.name = "World"
special2.special = special1

SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?

So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:

enum NormalArg: Equatable {
    case one(Int)
    case two

    static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
        switch (lhs, rhs) {
        case (let .one(a1), let .one(a2)):
            return a1 == a2
        case (.two,.two):
            return true
        default:
            return false
        }
    }
}
xxtesaxx
  • 6,175
  • 2
  • 31
  • 50
  • 2
    I didn't know I could add the `case` keyword on an if statement. – Zonily Jame May 19 '17 at 05:29
  • 1
    Oh sorry, I must have misunderstood you. The point is that swift does not know how to equate your enum by default if it has associated values (which the raw value one has). If your enum does not have associated values or if it has a raw-value type, you get equation for free by the standard library (by the RawRepresentable protocol I guess). You are surely aware that you cannot mix associated value enums with with raw types and thats why you cant compare ".one(Int) == .two" without implementing Equatable on your protocol by yourself. – xxtesaxx May 19 '17 at 12:59
  • From Swift 4.1 due to [SE-0185](https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md), Swift also supports synthesizing `Equatable` and `Hashable` for enums with associated values. – jedwidz Nov 22 '18 at 12:49
  • 1
    It's utterly horrible to read (and write), but it works. Thank you! – Womble Aug 09 '19 at 04:04
  • 3
    I don't know who thought about this syntax (if case .option = enum) but it's horrible, the IDE can't even guess which enum you're trying to check to give u options, and if you don't know options of this enum, you'll have to dig deep and waste a bunch of minutes. – Tamim Attafi Sep 15 '20 at 12:54
  • Swift documentation on this concept is [here](https://docs.swift.org/swift-book/ReferenceManual/Patterns.html#ID520). – Andrew Kirna Oct 30 '21 at 00:15
  • How do you use this concept for conditional modifiers? – Björn Mar 20 '22 at 14:35
13

The answer is Equatable Protocol.

Now let's see how it works.

Consider this enum for example:

enum Barcode {
    case upca(Int, Int)
    case qrCode(String)
    case none
}

If we check the equatable operator == on the enum it will fail.

// Error: binary operator '==' cannot be applied to two Barcode operands
Barcode.qrCode("code") == Barcode.qrCode("code")

How to fix this using Equatable Protocol?

extension Barcode: Equatable {
}

func ==(lhs: Barcode, rhs: Barcode) -> Bool {
    switch (lhs, rhs) {
    case (let .upca(codeA1, codeB1), let .upca(codeA2, codeB2)):
        return codeA1 == codeA2 && codeB1 == codeB2

    case (let .qrCode(code1), let .qrCode(code2)):
        return code1 == code2

    case (.None, .None):
        return true

    default:
        return false
    }
}

Barcode.qrCode("code") == Barcode.qrCode("code") // true
Barcode.upca(1234, 1234) == Barcode.upca(4567, 7890) // false
Barcode.none == Barcode.none // true
aashish tamsya
  • 4,903
  • 3
  • 23
  • 34