While building a very simple caching system, I came across a scenario where I have a slice that, as I iterate over it, gets consumed. New incoming data gets appended onto the slice and sendoff is handled by sending off slice[0] (assuming len() > 0 checks out) and rebuilding the slice with myslice = myslice[1:]. I re-implemented something similar here:
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
//for i := 0; i < len(finval); i++{
for i := 0; i < lenfin; i++ {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
}
}
I noticed that, while playing around with this simplified version, the commented out line re-checks the length of finval while iterating, leading to the output
0 : 1
1 : 2
2 : 3
3 : 4
instead of the expected and appropriate
0 : 1
1 : 2
2 : 3
3 : 4
4 : 5
5 : 6
6 : 7
It seems obvious to me that as each loop cycles over, it re-checks the length. I’m not sure why, but I would have expected the length value to be checked at the start and not changed. Am I wrong to expect this? Is there a technical reason this is implemented this way? I would have thought that some black magic involving pointers would be needed to have it re-check every loop.
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
var i int = 0
while i < lenfin {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
i++
}
}
if you check len in loop will like this:
func main() {
var finval []int = []int{1, 2, 3, 4, 5, 6, 7}
var lenfin = len(finval)
var i int = 0
while i < lenfin {
fmt.Println(i, ":", finval[0])
finval = finval[1:]
lenfin = len(finval) // here check again
i++
}
}
func main() {
shiftingTarget := 100
for i := 0; i < shiftingTarget; i++ {
shiftingTarget--
}
fmt.Println("All done and shifting target is", shiftingTarget)
}
That behaves exactly how you would expect, right? And if you invert it to for i := 0; shiftingTarget > i; i++ { you would ALSO expect it to work, right? So how would the compiler know which properties you expect to be constant? From the tour of go (emphasis mine):
The basic for loop has three components separated by semicolons:
the init statement: executed before the first iteration
the condition expression: evaluated before every iteration
the post statement: executed at the end of every iteration
There’s nothing special about the init/condition/post statement. You could write the following if you wanted:
func main() {
i := 0
for fmt.Println("init"); condition(); fmt.Println("post") {
if i == 10 {
break
}
i++
}
}
func condition() bool {
fmt.Println("condition")
return true
}
That said, I think your loop would probably be more accurately and simply represented like this:
func main() {
finval := []int{1, 2, 3, 4, 5, 6, 7}
// Process all the items in our slice
for len(finval) > 0 {
fmt.Println(finval[0])
finval = finval[1:]
}
}