25

I want to create a protocol that enforces a certain case on all enums conforming to this protocol.

For example, if I have a enum like this:

enum Foo{
    case bar(baz: String)
    case baz(bar: String)
}

I want to extend it with a protocol that adds another case:

case Fuzz(Int)

Is this possible?

nhgrif
  • 61,578
  • 25
  • 134
  • 173
cfischer
  • 24,452
  • 37
  • 131
  • 214

4 Answers4

46

Design

The work around is to use a struct with static variables.

Note: This is what is done in Swift 3 for Notification.Name

Below is an implementation on Swift 3

Struct:

struct Car : RawRepresentable, Equatable, Hashable, Comparable {

    typealias RawValue = String

    var rawValue: String

    static let Red  = Car(rawValue: "Red")
    static let Blue = Car(rawValue: "Blue")

    //MARK: Hashable

    var hashValue: Int {
        return rawValue.hashValue
    }

    //MARK: Comparable

    public static func <(lhs: Car, rhs: Car) -> Bool {

        return lhs.rawValue < rhs.rawValue
    }

}

Protocol

protocol CoolCar {

}

extension CoolCar {

    static var Yellow : Car {

        return Car(rawValue: "Yellow")
    }
}

extension Car : CoolCar {

}

Invoking

let c1 = Car.Red


switch c1 {
case Car.Red:
    print("Car is red")
case Car.Blue:
    print("Car is blue")
case Car.Yellow:
    print("Car is yellow")
default:
    print("Car is some other color")
}

if c1 == Car.Red {
    print("Equal")
}

if Car.Red > Car.Blue {
    print("Red is greater than Blue")
}

Note:

Please note this approach is not a substitute for enum, use this only when the values are not known at compile time.

user1046037
  • 16,755
  • 12
  • 92
  • 138
  • Why leave `protocol CoolCar` empty, just to extend it...? – Marty Jun 14 '18 at 09:15
  • This is just to show that it is possible to extend and this solution would work even with an extension. CoolCar might be defined in a framework (example: Standard Library / Foundation) and you might not be able to directly modify it. You can extend it and this solution would still work – user1046037 Jun 15 '18 at 04:23
  • 1
    How can we make case like Car.Red(parameter: String) from this structure? – Bishow Gurung Jul 26 '18 at 06:56
  • 1
    Issue is that now you will need to always define default for switch. – Roman Roba Mar 01 '19 at 16:58
  • that switch won't work with uitableview. If I do the same and I need to have switch in cellforrow to check what section it is, it won't work – Arun Kumar Jan 14 '20 at 20:07
13

no, since you can't declare a case outside of an enum.

R Menke
  • 8,183
  • 4
  • 35
  • 63
7

An extension can add a nested enum, like so:

enum Plants {
  enum Fruit {
     case banana
  }
} 


extension Plants {
  enum Vegetables {
     case potato
  }
}
jrbedard
  • 3,662
  • 5
  • 30
  • 34
Mark
  • 103
  • 1
  • 6
  • How would I assign a variable with banana or potato? And what would the type of that variable be? – daniel Jul 18 '23 at 00:55
3

Here are a couple additional takes that may help somebody out there:

Using your example:

enum Foo {
    case bar(baz: String)
    case baz(bar: String)
} 

You can consider to "nest" it in a case of your own enum:

enum FooExtended {
    case foo(Foo) // <-- Here will live your instances of `Foo`
    case fuzz(Int)
}

With this solution, it becomes more laborious to access the "hidden" cases associated type. But this simplification could actually be beneficial in certain applications.

Another alternative passes by just recreate and extend it while having a way to convert Foo into the extended enum FooExtended (eg. with a custom init):

enum FooExtended {
    case bar(baz: String)
    case baz(bar: String)
    case fuzz(Int)

    init(withFoo foo: Foo) {
        switch foo {
        case .bar(let baz):
            self =  .bar(baz: baz)
        case .baz(let bar):
            self = .baz(bar: bar)
        }
    }
}

There may be many places where one, the other, or both of these solutions make absolutely no sense, but I'm pretty sure they may be handy to somebody out there (even if only as an exercise).

fbeeper
  • 238
  • 2
  • 10