Question:
When attempting to stride over String.CharacterView.Index indices by e.g. a stride of 2
extension String.CharacterView.Index : Strideable { } let str = "01234" for _ in str.startIndex.stride(to: str.endIndex, by: 2) { } // fatal error I get the following runtime exception
fatal error: cannot increment endIndex
Just creating the StrideTo<String.CharacterView.Index> above, however, (let foo = str.startIndex.stride(to: str.endIndex, by: 2)) does not yield an error, only when attempting to stride/iterate over or operate on it (.next()?).
- What is the reason for this runtime exception; is it expected (mis-use of conformance to
Stridable)?
I'm using Swift 2.2 and Xcode 7.3. Details follow below.
Edit addition: error source located
Upon reading my question carefully, it would seem as if the error really does occur in the next() method of StrideToGenerator (see bottom of this post), specifically at the following marked line
let ret = current current += stride // <-- here return ret Even if the last update of current will never be returned (in next call to next()), the final advance of current index to a value larger or equal to that of _end yields the specific runtime error above (for Index type String.CharacterView.Index).
(0..<4).startIndex.advancedBy(4) // OK, -> 4 "foo".startIndex.advancedBy(4) // fatal error: cannot increment endIndex However, one question still remains:
- Is this a bug in the
next()method ofStrideToGenerator, or just an error that pops up due to a mis-use ofString.CharacterView.Indexconformance toStridable?
Related
The following Q&A is related to the subject of a iterating over characters in steps other than +1, and worth including in this question even if the two questions differ.
Especially note @Sulthan:s neat solution in the thread above.
Details
(Apologies for hefty details/investigations of my own, just skip these sections if you can answer my question without the details herein)
The String.CharacterView.Index type describes a character position, and:
- conforms to
Comparable(and in so,Equatable), - contains implementations for
advancedBy(_:)anddistanceTo(_:).
Hence, it can directly be made to conform to the protocol Strideable, making use of Stridable:s default implementations of methods stride(through:by:) and stride(to:by:). The examples below will focus on the latter (analogous problems with the former):
...
func stride(to end: Self, by stride: Self.Stride) -> StrideTo<Self>Returns the sequence of values (self, self + stride, self + stride + stride, ... last) where last is the last value in the progression that is less than end.
Conforming to Stridable and striding by 1: all good
Extending String.CharacterView.Index to Stridable and striding by 1 works fine:
extension String.CharacterView.Index : Strideable { } var str = "0123" // stride by 1: all good str.startIndex.stride(to: str.endIndex, by: 1).forEach { print($0,str.characters[$0]) } /* 0 0 1 1 2 2 3 3 */ For an even number of indices in str above (indices 0..<4), this also works for a stride of 2:
// stride by 2: OK for even number of characters in str. str.startIndex.stride(to: str.endIndex, by: 2).forEach { print($0,str.characters[$0]) } /* 0 0 2 2 */ However, for some cases of striding by >1: runtime exception
For an odd number of indices and a stride of 2, however, the stride over the character views indices yield a runtime error
// stride by 2: fatal error for odd number of characters in str. str = "01234" str.startIndex.stride(to: str.endIndex, by: 2).forEach { print($0,str.characters[$0]) } /* 0 0 2 2 fatal error: cannot increment endIndex */ Investigations of my own
My own investigations into this made me suspect the error comes from the next() method of the StrideToGenerator structure, possibly when this method calls += on the stridable element
public func += <T : Strideable>(inout lhs: T, rhs: T.Stride) { lhs = lhs.advancedBy(rhs) } (from a version of the Swift source for swift/stdlib/public/core/Stride.swift that somewhat corresponds to Swift 2.2). Given the following Q&A:s
- Trim end off of string in swift, getting error at runtime,
- Swift distance() method throws fatal error: can not increment endIndex,
we could suspect that we would possibly need to use String.CharacterView.Index.advancedBy(_:limit:) rather than ...advancedBy(_:) above. However from what I can see, the next() method in StrideToGenerator guards against advancing the index past the limit.
Edit addition: the source of the error seems to indeed be located in the next() method in StrideToGenerator:
// ... in StrideToGenerator public mutating func next() -> Element? { if stride > 0 ? current >= end : current <= end { return nil } let ret = current current += stride /* <-- will increase current to larger or equal to end if stride is large enough (even if this last current will never be returned in next call to next()) */ return ret } Even if the last update of current will never be returned (in next call to next()), the final advance of current index to a value larger or equal to that of end yields the specific runtime error above, for Index type String.CharacterView.Index.
(0..<4).startIndex.advancedBy(4) // OK, -> 4 "foo".startIndex.advancedBy(4) // fatal error: cannot increment endIndex Is this to be considered a bug, or is String.CharacterView.Index simply not intended to be (directly) conformed to Stridable?