Enums - RaduG/swift_learning GitHub Wiki

Basic syntax

enum SomeEnum {
  // enum code
}

Enumeration items are defined using case.

enum CompassPoint {
  case north
  case east
  case south
  case west
}

// or

enum CompassPoint {
  case north, east, south, west
}

Case values

The cases don't need to hold explicit values, as each case is a standalone type.

let direction = CompassPoint.east

// or, if the type can be inferred, the enum name can be dropped

let otherDirection: CompassPoint = .north

Matching cases

Use switch:

func whereAmIGoing(_ to: CompassPoint) {
  switch to {
  case .north:
    print("You are going north")
  case .east:
    print("You are going east")
  case .south:
    print("You are going south")
  case .west:
    print("You are going west")
  }
}

whereAmIGoing(.east) // or whereAmIGoing(CompassPoint.east)

Iterable enums

To make an Enum iterable, have the type implement the CaseIterable protocol. The compiler automatically adds an implementation. The result is having the allCases attribute, which can be iterated over:

enum CompassPoint: CaseIterable {
  case north, east, south, west
}

for c in CompassPoint.allCases {
  whereAmIGoing(c)
}

Associated values

It is also possible to store additional data with cases.

enum Colour {
  case rgb(Int, Int, Int)
  case cmyk(Double, Double, Double, Double)
  case named(String)
}

let white = Colour.rgb(255, 255, 255)
let electricGreen = Colour.cmyk(1, 0, 1, 0)
let red = Colour.named("red")

This can be used in pattern matching:

func hasRed(_ colour: Colour) -> Bool {
  switch colour {
  case .rgb(let red, _, _) where red > 0:
    return true
  case .named("red"):
    return true
  default:
    return false
  }
}

hasRed(Colour.rgb(0, 100, 200)) // false
hasRed(Colour.rgb(10, 0, 0)) // true
hasRed(Colour.cmyk(0.2, 0.3, 0.75, 0)) // false
hasRed(Colour.named("red")) // true

If all the associated values should be captured as part of the pattern match, use a single var/let:

func printIsGray(_ colour: Colour) {
  switch colour {
  case let .rgb(red, green, blue) where (
      red > 0 
      && red < 255
      && red == green 
      && green == blue
  ):
    print("\(colour) is gray")
  case let .cmyk(cyan, magenta, yellow, black) where (
    cyan == 0 
    && cyan == magenta 
    && magenta == yellow 
    && black > 0
    && black < 1
  ):
    print("\(colour) is gray")
  case .named("gray"), .named("grey"):
    print("\(colour) is gray")
  default:
    print("\(colour) is not gray")
  }
}

printIsGray(Colour.rgb(255, 255, 255))
printIsGray(Colour.rgb(245, 245, 245))
printIsGray(Colour.cmyk(0, 0, 0, 0))
printIsGray(Colour.cmyk(0, 0, 0, 1))
printIsGray(Colour.cmyk(0, 0, 0, 0.75))
printIsGray(Colour.named("gray"))
printIsGray(Colour.named("grey"))
printIsGray(Colour.named("blue"))

Raw values

Cases can also have values. Those are constants and are effectively static values associated to each case. For example:

enum WhitespaceSequence: String {
  case tab = "\t"
  case space = " "
  case newLine = "\n"
  case carriageReturn = "\r"
}

If the type is Int, values can be automatically assigned consecutively:

enum Rank: Int, CaseIterable {
  case two = 2, three, four, five, six, seven, eight, nine, ten
  case jack = 12, queen, king, ace
}

for rank in Rank.allCases {
  print("\(rank): \(rank.rawValue)")
}

Note that the raw value can be accessed via the .rawValue attribute of an enum case.

Enums with raw values also have a constructor taking a raw value as argument and creating an instance of its matching enum case.

// Returns an optional
print(Rank(rawValue: 7)!)

Recursive enumerations

Enum cases can refer to other enum cases, creating recursive definitions. Recursive cases must be marked with the indirect keyword

enum ArithmeticExpression {
  case number(Int)
  indirect case addition(ArithmeticExpression, ArithmeticExpression)
  indirect case subtraction(ArithmeticExpression, ArithmeticExpression)
}
 
 // or
 
indirect enum ArithmeticExpression {
  case number(Int)
  case addition(ArithmeticExpression, ArithmeticExpression)
  case subtraction(ArithmeticExpression, ArithmeticExpression)
}
enum ArithmeticExpression {
  case number(Int)
  indirect case addition(ArithmeticExpression, ArithmeticExpression)
  indirect case subtraction(ArithmeticExpression, ArithmeticExpression)

  func describe() -> String {
    switch self {
    case let .number(value):
      return "\(value)"
    case let .addition(lhs, rhs):
      return "\(lhs.describe()) + \(rhs.describe())"
    case let .subtraction(lhs, rhs):
      return "\(lhs.describe()) - \(rhs.describe())"
    }
  }
}

let exp: ArithmeticExpression = .addition(
  .subtraction(
    .number(10), .addition(
      .number(12), .number(15)
    )
  ),
  .number(25)
)

print(exp.describe())