Reputation: 2024
I have an enum like this:
enum Rank: CaseIterable {
case Ace, King, Queen, ...
}
and want to make it conform to Stridable
. I tried to implement the func distance(to other: _)
by getting the index of the card like so:
func distance(to other: Rank) -> Int {
let indexOfSelf = Rank.allCases.firstIndex(of: self)!
let indexOfOther = Rank.allCases.firstIndex(of: other)!
return indexOfOther - indexOfSelf
}
This works fine and as intended as long as I don't conform to Stridable, e.g. ace.distanceTo(Queen)
results in 2. However, as soon as I conform to Stridable
, the code breaks into an infinite loop and I get the error EXC_BAD_ACCESS (code=2, address=x)
.
Is that supposed to happen? And if so, why is that happening?
Thanks for all help!
My implementation of advanced(by n: Int)
:
func advanced(by n: Int) -> Rank {
let index = Rank.allCases.firstIndex(of: self)!
let resultIndex = index + n
if resultIndex > Rank.allCases.count {
return .Two
}
return Rank.allCases[resultIndex]
}
something that would cause the error:
call of: ace.distanceTo(Queen)
would break into an infinite loop
Upvotes: 3
Views: 322
Reputation: 539995
From the documentation of the Strideable protocol:
Important
The Strideable protocol provides default implementations for the equal-to (==) and less-than (<) operators that depend on the Stride type’s implementations. If a type conforming to Strideable is its own Stride type, it must provide concrete implementations of the two operators to avoid infinite recursion.
In your case Rank
is not it's own Stride
type, but the distance(to:)
method calls firstIndex(of:)
, which calls ==
on the elements. Now ==
has a special implementation for Strideable
types which – as we can see from the implementation in Stride.swift– calls distance(to:)
. That leads to an “infinite” recursion, and ultimately to an stack overflow. You can also see that from the stack backtrace:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeef3fffb8)
* frame #0: 0x0000000100001b4b test`Rank.distance(other=ace, self=king) at main.swift:11
frame #1: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
frame #2: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
frame #3: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
frame #4: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
frame #5: 0x0000000100001c31 test`Rank.distance(other=ace, self=ace) at main.swift:12:49
...
frame #42256: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
frame #42257: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
frame #42258: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
frame #42259: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
frame #42260: 0x0000000100001c31 test`Rank.distance(other=ace, self=ace) at main.swift:12:49
frame #42261: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
frame #42262: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
frame #42263: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
frame #42264: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
frame #42265: 0x0000000100001c31 test`Rank.distance(other=queen, self=ace) at main.swift:12:49
frame #42266: 0x0000000100001830 test`main at main.swift:23:18
frame #42267: 0x00007fff7b3843d5 libdyld.dylib`start + 1
(lldb)
As Joakim said, the easiest solution is to implement those methods based on the enums's raw values, this avoids the (recursive) use of ==
:
enum Rank: Int, Strideable {
case ace, king, queen, jack
func advanced(by n: Int) -> Rank {
return Self(rawValue: self.rawValue + n)!
}
func distance(to other: Rank) -> Int {
return other.rawValue - self.rawValue
}
}
This is also more efficient than looking up the values in the allCases
collection again and again.
Upvotes: 3