Search code examples
swiftswitch-statementstatic-variables

Swift static var in switch case


switch-case for comparing static vars isn't working as expected. However, specifying a type or comparing directly works. Please see below:

class AnimalHelper {
    func loadAnimal(_ animal: Animal) {

        // Doesn't compile
        switch animal {
            case .squirrel, .deer: loadGrass()
            case .dolphin: return // not implemented
            default: loadMeat()
        }

        // Direct comparison works
        if animal == .squirrel || animal == .deer {
            loadGrass()
        } else if animal == .dolphin {
            return // not implemented
        } else {
            loadMeat()
        }

        // Specifying the type explicitly also works
        switch animal {
            case Animal.squirrel, Animal.deer: loadGrass()
            case Animal.dolphin: return // not implemented
            default: loadMeat()
        }
    }

    func loadGrass() {}
    func loadMeat() {}
}

class Animal {
    let id = 6 // Will be generated
    let hasFourLegs = true
    let numberOfEyes = 2
    // ... //

    static var squirrel: Animal { return .init() }
    static var dolphin: Animal  { return .init() }
    static var puma: Animal { return .init() }
    static var deer: Animal { return .init() }
}

extension Animal: Equatable {
    public static func ==(lhs: Animal, rhs: Animal) -> Bool {
        return lhs.id == rhs.id
    }
}

I'm sure something about the above isn't quite right because of which I'm getting the following compilation errors:

Enum case 'squirrel' not found in type 'Animal'
Enum case 'deer' not found in type 'Animal'
Enum case 'dolphin' not found in type 'Animal' 

Please let me know how is checking for equality in a switch-case is different from that in an if condition.


Solution

  • In Swift, switch-case may use several different rules to match the switch-value and the case labels:

    • enum case matching

      In this case, you can use dot-leaded case labels, but unfortunately, your Animal is not enum.

      (This is not the same as equality below, as enum cases may have associated values.)

    • pattern matching operator ~=

      Swift searches an overload for the type of the switch-value and the type of case-label, if found, applies the operator and uses the the Bool result as indicating matches. For this purpose, Swift needs to infer the types of case-labels independent of the switch-value, thus Swift cannot infer the types of case-labels with dot-leaded notation.

    • equality ==

      When the switch-value is Equatable, Swift uses the equality operator == for matching the switch-value to the case-labels.

    (There may be more which I cannot think of now.)

    The detailed behavior is not well defined, but it's difficult for compilers to resolve two rules -- pattern matching and equality. (You may want to define custom matching with ~= for Equatable types.)

    So, in Swift 3, dot-leaded notation in the case-labels only works for enum types.

    But, as far as I checked, Swift 4 have made it. Try Xcode 9 (currently the latest is beta 3), and your code would compile. This behavior may change in the release version of Xcode 9, but you know how to work around.