0

I'm new to Swift and I'm exploring what happens if a collection is modified while iterating over it using a Range. I can't figure out why these two loops have different behavior:

var things = ["a", "b", "c"]
for i in 0..<things.count {
    print("i: \(i) count: \(things.count) value: \(things[i])")
    if i == 1 {
        things.append("x")
    }
}
// i: 0 count: 3 value: a
// i: 1 count: 3 value: b
// i: 2 count: 4 value: c

vs

var things = ["a", "b", "c"]
for i in things.startIndex...things.endIndex {
    print("i: \(i) count: \(things.count) value: \(things[i])")
    if i == 1 {
        things.append("x")
    }
}
// i: 0 count: 3 value: a
// i: 1 count: 3 value: b
// i: 2 count: 4 value: c
// i: 3 count: 4 value: x

I created this Array class extension so I could see when the Range literal accesses the array's properties:

extension Array {
    var myCount: Int {
        print("myCount (\(self.count))")
        return self.count
    }
    var myStartIndex: Int {
        print("myStartIndex (\(self.startIndex))")
        return self.startIndex
    }
    var myEndIndex: Int {
        print("myEndIndex (\(self.endIndex))")
        return self.endIndex
    }
}

If I use these properties instead of the normal ones, I get:

myCount (3)
i: 0 count: 3 value: a
i: 1 count: 3 value: b
i: 2 count: 4 value: c

and

myStartIndex (0)
myEndIndex (3)
i: 0 count: 3 value: a
i: 1 count: 3 value: b
i: 2 count: 4 value: c
i: 3 count: 4 value: x

I must be missing something, because this feels like magic! It seems like endIndex is being re-evaluated, but count is not. What gives?

1
  • for i in things.indices { or if you don't need the index for thing in things { Commented Dec 24, 2017 at 7:10

1 Answer 1

3

This is because in the first piece of code, you have used the half-open range operator (..<) while in the second piece of code you used the normal range operator (...). They are different. From the apple docs:

The half-open range operator (a..<b) defines a range that runs from a to b, but doesn’t include b.

So, the for loop in the first piece of code does not continue if things.count is 4 as the range (0 - 4) when used with the half-open range operator only loops when i is 0, 1, 2, or 3.

If you use ... instead of ..<, the result of the first piece of code will be the same as the second one.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for making me smack my head -- what a simple mistake! It seems that the only way to iterate over a changing collection is to use a while loop, because it evaluates its condition each time through the loop. Is that right -- for loops in Swift only evaluate once? (I'm not saying this is a good idea ;) -- just trying to learn the language)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.