John Difool
John Difool

Reputation: 5722

Negative ArraySlice: index is out of range

I can't figure out why I am getting the error on the second iteration in the loop. Can you help me understand where the problem comes from?

let NumTracks = 3
let TrackBytes = 2

func readBytes(input: [UInt8]?) {
    if let input = input  {
        var input = input[0..<input.count]
        for _ in 0..<NumTracks {
            print(input[0..<TrackBytes]) // fatal error: Negative ArraySlice index is out of range
            input = input[TrackBytes..<input.count]
        }
    }
}
let samples = [UInt8]?([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
readBytes(samples)

There is another test case like this one and no reason why this is crashing as well.

EDIT

I don't get the error when I use this code variation (and I still don't know why):

let NumTracks = 3
let TrackBytes = 2

func readBytes(input: [UInt8]?) {
    if let input = input  {
        var input = input[0..<input.count]
        for _ in 0..<NumTracks {
            print(input[input.startIndex..<input.startIndex.advancedBy(2)])
            input = input[input.startIndex.advancedBy(2)..<input.endIndex]
        }
    }
}
let samples = [UInt8]?([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
readBytes(samples)

Upvotes: 4

Views: 2531

Answers (1)

Martin R
Martin R

Reputation: 539965

The reason is that taking an array slice preserves the original array indices:

let array = [1, 2, 3, 4]
let slice = array[1 ..< 3]

print(slice) // [2, 3]
print(slice.startIndex) // 1
print(slice.endIndex)   // 3

print(slice[1])         // 2 (the first element of the slice)
print(slice[0])         // fatal error: Index out of bounds

In your case, after the first call to

input = input[TrackBytes..<input.count]

the first valid index for input is TrackBytes and not 0 and therefore the next call to

input[0..<TrackBytes]

causes the runtime error.

So the startIndex of a collection is not necessarily zero and you already found a solution, another one is

func readBytes(input: [UInt8]?) {
    if let input = input  {
        var input = input[0..<input.count]
        for _ in 0..<NumTracks {
            print([UInt8](input.prefix(TrackBytes))) 
            input = input.suffixFrom(input.startIndex + TrackBytes)
        }
    }
}

or even shorter, without modifying a local array slice repeatedly:

func readBytes(input: [UInt8]?) {
    if let input = input {
        for start in 0.stride(to: NumTracks * TrackBytes, by: TrackBytes) {
            print([UInt8](input[start ..< start + TrackBytes])) 
        }
    }
}

Upvotes: 8

Related Questions