Strideable conflicts with CaseIterable?

I have the following code snippet that works fine:

enum LetterGrade: String, CaseIterable, Comparable {
	case a = "A"
	case b = "B"
	case c = "C"
	case d = "D"
	case f = "F"
	
	static func < (lhs: Self, rhs: Self) -> Bool {
		return lhs.rawValue > rhs.rawValue
	}
	
	func translatedToNumericGrade() -> Int {
		let sortedLetterGrades = Self.allCases.sorted()
		return sortedLetterGrades.firstIndex(of: self)!
	}
}

print(LetterGrade.a.translatedToNumericGrade()) // Prints 4

When I add Strideable conformance to it, it starts to have runtime errors:

enum LetterGrade: String, CaseIterable, Strideable {
	case a = "A"
	case b = "B"
	case c = "C"
	case d = "D"
	case f = "F"
	
	static func < (lhs: Self, rhs: Self) -> Bool {
		return lhs.rawValue > rhs.rawValue
	}
	
	typealias Stride = Int
	
	func distance(to other: Self) -> Stride {
		let sortedLetterGrades = Self.allCases.sorted() 
		//                                     ^^^^^^ 
		// EXC_BAD_ACCESS (code=2, address=<some hex value>)
		let otherIndex = sortedLetterGrades.firstIndex(of: other)!
		let selfIndex = sortedLetterGrades.firstIndex(of: self)!
		return abs(selfIndex - otherIndex)
	}
	
	func advanced(by n: Stride) -> Self {
		let sortedLetterGrades = Self.allCases.sorted()
		let selfIndex = sortedLetterGrades.firstIndex(of: self)!
		return sortedLetterGrades[selfIndex + n]
	}
	
	func translatedToNumericGrade() -> Int {
		let sortedLetterGrades = Self.allCases.sorted()
		return sortedLetterGrades.firstIndex(of: self)!
	}
}

print(LetterGrade.a.translatedToNumericGrade())

If I don't access allCases in distance(to:) and advanced(by:), a different error shows up in translatedToNumericGrade:

enum LetterGrade: String, CaseIterable, Strideable {
	case a = "A"
	case b = "B"
	case c = "C"
	case d = "D"
	case f = "F"
	
	static func < (lhs: Self, rhs: Self) -> Bool {
		return lhs.rawValue > rhs.rawValue
	}
	
	typealias Stride = Int
	
	func distance(to other: Self) -> Stride {
		return 2
	}
	
	func advanced(by n: Stride) -> Self {
		return .f
	}
	
	func translatedToNumericGrade() -> Int {
		let sortedLetterGrades = Self.allCases.sorted()
		return sortedLetterGrades.firstIndex(of: self)!
		//                                            ^
		// Fatal error: Unexpectedly found nil while unwrapping an Optional value
	}
}

print(LetterGrade.a.translatedToNumericGrade())

This happens even if I manually provide CaseIterable conformance:

enum LetterGrade: String, CaseIterable, Strideable {
	case a = "A"
	case b = "B"
	case c = "C"
	case d = "D"
	case f = "F"
	
	typealias AllCases = [Self]
	
	static var allCases: AllCases = [.a, .b, .c, .d, .f]
	
	static func < (lhs: Self, rhs: Self) -> Bool {
		return lhs.rawValue > rhs.rawValue
	}
	
	typealias Stride = Int
	
	func distance(to other: Self) -> Stride {
		return 2
	}
	
	func advanced(by n: Stride) -> Self {
		return .f
	}
	
	func translatedToNumericGrade() -> Int {
		let sortedLetterGrades = Self.allCases.sorted()
		return sortedLetterGrades.firstIndex(of: self)!
		//                                            ^
		// Fatal error: Unexpectedly found nil while unwrapping an Optional value
	}
}

print(LetterGrade.a.translatedToNumericGrade())

Did I use CaseIterable or Strideable incorrectly, or do they conflict with each other?

== in Strideable uses distance(to:). You just hit infinite recursion in the first case, and correctly get nil in the second. A possible fix would be to add your own definition of ==:

static func ==(lhs: Self, rhs: Self) -> Bool {
  return lhs.rawValue == rhs.rawValue
}
2 Likes