Today I had to track the most puzzling bug. My code was like this
var i int
for i = range v {
if v[i].str == ... {
break
}
}
if i == len(v) {
// never executed
}
The problem is that when the range of the for loop reaches the end of v, i == len(v)-1
, not len(v)
. This is unexpected.
Switching to a more conventional for loop instruction fixed the issue.
Beware, don't make the same error as me. It'll save you debugging time.
var i int
for i = 0; i < len(v); i++ {
...
**评论:**
lstokeworth:
Kraigius:It works as described in the spec:
For an array, pointer to array, or slice value a, the index iteration values are produced in increasing order, starting at element index 0. If at most one iteration variable is present, the range loop produces iteration values from 0 up to len(a)-1 and does not index into the array or slice itself. For a nil slice, the number of iterations is 0.
sheepdog69:and does not index into the array or slice itself.
What does this passage mean?
Kraigius:If you have only 1 variable on the left side,
range
will only give you the indexes into the slice, not the actual values. To get the values, you need to supply 2 variables on the left side - one for the index, and one for the variable.Hope that was clear.
__CAFxX:Ohhh, that. I knew the spec was doing that but the words just wasn't making sense when I was reading them. Thanks for the answer.
chmikes:You should use a flag to make the intent clear...
found := false for _, e := range v { if e.str == ... { found = true break } } if !found { // the element was not found }
MalkMalice:Excellent suggestion. That would indeed be more readable. I'll remember that solution.
seanpfeifer:Is there a reason you don't do:
for _, val := range v { if val.str == ... { break } }
OP is looking to use the index value after they complete their loop. Eg, to maybe so they know what items haven't been processed by the loop to handle the leftovers separately.
The issue they ran into is that using range does not give the same ending index as using a simple incrementing for loop comparing vs slice length. So if you have a slice of len(v)=5:
- for i = range(v){} over the entire slice will end with an index of 4 (len(v)-1).
- for i = 0; i < len(v); i++ {} over the slice will end with an index value of 5 (len(v), after the conditional fails and the loop exits).
More info about the behavior for range is specified in https://golang.org/ref/spec#RangeClause
MalkMalice:the range loop produces iteration values from 0 up to len(a)-1
sheepdog69:Ah, thanks for the explanation!
chmikes:Were you expecting range to go past the end of the slice? Remember, the last element in the slice is at index
len(v)-1
, not atlen(v)
.
JavaSuck:I didn't knew that. Do you know why it's like that ?
Tacticus:read that as "bug that caught me with rage" :D
chmikes:
for i = 0; i < len(v); i++ {
This for loop will also have the last loop at i == len(v)-1 you kinda needfor i = 1; i <= len(v); i++ {
Alternatively you can use a two item range loop.for i, v = range vs { if v.str == ... { break } } // then look at this after the loop? if i == len(v) { // never executed }
You are right, but I wanted i to iterate from 0 to len(v) while looping. This is because I'm scanning the elements of v to find something. When the loop ends, and we use the C like for loop, i is incremented and equal to len(v). I use this condition to determine if nothing was found. Otherwise i < len(v) because of the break instruction.
The suggestion to use a found bool would make the code more readable and the intent clearer. I could also have written this
itemPos := -1 for i := range v { if v[i].str == ... { // item found at i itemPos = i break } } if itemPos == -1 { // item not found }
I also didn't want to use the two item range because v is a slice of struct with four fields. It would have copied the struct just to test one field. That is why I use only the index. If v was a slice of pointers to the struct I would have done as you suggest.
Thank you for the help.
