somu
(somu)
1
Hi,
Pardon my ignorance, I haven't used Array.Index and just used it.
I am encountering Index out of range error when I didn't expect it
My Expectation
- In the following code I thought
i would become nil
Actual Result
- In the following code
i was 2
Question
- What am I missing?
- Is it safe to use Array.Index, I feel like the same checking needs to be done manually or am I using the API incorrectly?
Code
Given below is the code based on the documentation code (slightly modified):
Refer: index(_:offsetBy:limitedBy:) | Apple Developer Documentation
let numbers = [10, 20, 30, 40, 50]
if let i = numbers.index(numbers.startIndex,
offsetBy: 5,
limitedBy: numbers.endIndex) {
print(numbers[i]) //Fatal error: Index out of range
}
vns
2
endIndex is not exactly what you expect it to be - it's past the end (endIndex | Apple Developer Documentation). And index method you are using expects limitedBy parameter to be a valid array index so it could use it as a fallback. So you need to pass numbers.index(before: numbers.endIndex) instead.
1 Like
somu
(somu)
3
Thanks a lot @vns I was breaking my head over it.
I see the usefulness partly but seems very verbose are there scenarios where this proves better than manual checking?
It's more useful for generic functions that take arbitrary collections where the index type isn't necessarily an integer, unlike Array.
4 Likes
somu
(somu)
5
Thanks a lot @David_Smith I didn't beyond integer index.. wow!
I see your point now, yeah non-integer indexes my logic of manually checking wouldn't be possible, thanks!!
For integer indexes is it ok to check to use manual checking? (as shown below)
This uses numbers.startIndex and numbers.endIndex
let numbers = [10, 20, 30, 40, 50]
let offset = 5
if offset < numbers.endIndex {
let index = offset + numbers.startIndex
print(numbers[index])
}
Seems fine to me. Array also always has a startIndex of 0, so you can simplify further. ArraySlice is the one that might not start at 0.
1 Like
somu
(somu)
7
yeah I didn't even have to add numbers.startIndex .. haha
vns
8
Yes, I almost never use these methods on Arrays, since it much simpler to express it via simple arithmetic. When it comes to String for example, or working with generic collections, it's a powerful API on collection, and you it's good you are getting more familiar with it anyway.
1 Like
somu
(somu)
9
Thanks a lot @vns and @David_Smith was a good learning for me!!
ibex10
10
Better check that index first. 
let numbers = [10, 20, 30, 40, 50]
let offset = 5
let index = offset + numbers.startIndex
if index >= numbers.startIndex && index < numbers.endIndex {
print(numbers[index])
}
hisekaldma
(Jonathan Hise Kaldma)
11
The docs here could definitely be clearer! The examples certainly suggest that you can use endIndex as the limit and get back an index that’s safe to use for subscripting, when in fact you can’t. Looks like there’s a bug tracking this: [SR-10487] Misleading about the swift doc. · Issue #52887 · apple/swift · GitHub.
3 Likes
somu
(somu)
12
Thanks a lot @hisekaldma I totally agree, the example in the documentation could be clearer.
I understood it completely wrong initially, I am glad that there is an issue tracking it, hope the documentation gets updated with a clearer example.
vns
13
Kinda missed that this is from the documentation…
AlexanderM
(Alexander Momchilov)
14
Just to colour in some reasoning behind this, which also helps with remembering it:
Setting the endIndex to "one past the end" lets you write code that doesn't need to have a special case for empty collections. std::end does the same in C++
If we instead define endIndex to be "the last element valid instead", then it breaks down for empty collections: because they don't have any elements to point to, you'd need to implement a special-case for it, which must break the rule.
You could design this with 3 possible values (that I can think of), which all suck:
0: in that case, then you can't distinguish between "has one element, at index 0", or "has no elements"
-1: now you have cases where you end index can be smaller than your start index
- Some other sentinel like
Int.min
In any case, you need to litter you code with special cases for empty collections.
With the current design, startIndex..<endIndex is always valid for Array.
8 Likes
somu
(somu)
15
Thanks a lot @AlexanderM that was clear explanation.
The part that tripped me was the documentation index(_:offsetBy:limitedBy:) | Apple Developer Documentation
In my humble opinion by using limitedBy: numbers.endIndex is not a good idea
Given below is from the documentation, just changing the offset would cause it to crash.
let numbers = [10, 20, 30, 40, 50]
if let i = numbers.index(numbers.startIndex,
offsetBy: 4,
limitedBy: numbers.endIndex) {
print(numbers[i])
}
i just really really wish that doccomment had an example of the boundary condition, that shows you get nil when you pass 5 for the offsetBy, because i remember being confused about the same thing when i was first starting to use Swift.
the documentation for String.index(_:offsetBy:limitedBy:) has the same issue, it gives examples for offsetBy: 4 and offsetBy: 6, but not 5 for some reason.
2 Likes
somu
(somu)
17
Thanks a lot @taylorswift you are spot on!
Yes I agree, they should have clearly explained it with 5 stating that it would be accessing beyond the bounds if limitedBy: numbers.endIndex is used
rayx
(Huan Xiong)
18
An alternative way to think about it is that the design is consistent with (or an example of) preferring half-open interval over closed-interval.
2 Likes