Bug that caught me with range ...

agolangf · · 430 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Today I had to track the most puzzling bug. My code was like this</p> <pre><code>var i int for i = range v { if v[i].str == ... { break } } if i == len(v) { // never executed } </code></pre> <p>The problem is that when the range of the for loop reaches the end of v, <code>i == len(v)-1</code>, not <code>len(v)</code>. This is unexpected. </p> <p>Switching to a more conventional for loop instruction fixed the issue. </p> <p>Beware, don&#39;t make the same error as me. It&#39;ll save you debugging time.</p> <pre><code>var i int for i = 0; i &lt; len(v); i++ { ... </code></pre> <hr/>**评论:**<br/><br/>lstokeworth: <pre><p>It works as <a href="https://golang.org/ref/spec#For_range" rel="nofollow">described in the spec</a>: </p> <blockquote> <p>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.</p> </blockquote></pre>Kraigius: <pre><blockquote> <p>and does not index into the array or slice itself.</p> </blockquote> <p>What does this passage mean?</p></pre>sheepdog69: <pre><p>If you have only 1 variable on the left side, <code>range</code> 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.</p> <p>Hope that was clear.</p></pre>Kraigius: <pre><p>Ohhh, that. I knew the spec was doing that but the words just wasn&#39;t making sense when I was reading them. Thanks for the answer.</p></pre>__CAFxX: <pre><p>You should use a flag to make the intent clear... </p> <pre><code>found := false for _, e := range v { if e.str == ... { found = true break } } if !found { // the element was not found } </code></pre></pre>chmikes: <pre><p>Excellent suggestion. That would indeed be more readable. I&#39;ll remember that solution. </p></pre>MalkMalice: <pre><p>Is there a reason you don&#39;t do:</p> <p><code> for _, val := range v { if val.str == ... { break } } </code></p></pre>seanpfeifer: <pre><p>OP is looking to use the index value after they complete their loop. Eg, to maybe so they know what items haven&#39;t been processed by the loop to handle the leftovers separately.</p> <p>The issue they ran into is that using <em>range</em> does not give the same ending index as using a simple incrementing <em>for</em> loop comparing vs slice length. So if you have a slice of len(v)=5:</p> <ul> <li><em>for i = range(v){}</em> over the entire slice will end with an index of <strong>4</strong> (len(v)-1).</li> <li><em>for i = 0; i &lt; len(v); i++ {}</em> over the slice will end with an index value of <strong>5</strong> (len(v), after the conditional fails and the loop exits).</li> </ul> <p>More info about the behavior for range is specified in <a href="https://golang.org/ref/spec#RangeClause" rel="nofollow">https://golang.org/ref/spec#RangeClause</a></p> <blockquote> <p>the range loop produces iteration values from 0 up to len(a)-1</p> </blockquote></pre>MalkMalice: <pre><p>Ah, thanks for the explanation!</p></pre>sheepdog69: <pre><p>Were you expecting range to go past the end of the slice? Remember, the last element in the slice is at index <code>len(v)-1</code>, not at <code>len(v)</code>.</p></pre>chmikes: <pre><p>I didn&#39;t knew that. Do you know why it&#39;s like that ?</p></pre>JavaSuck: <pre><p>read that as &#34;bug that caught me with rage&#34; :D</p></pre>Tacticus: <pre><p><code>for i = 0; i &lt; len(v); i++ {</code> This for loop will also have the last loop at i == len(v)-1 you kinda need <code>for i = 1; i &lt;= len(v); i++ {</code> Alternatively you can use a two item range loop. </p> <pre><code>for i, v = range vs { if v.str == ... { break } } // then look at this after the loop? if i == len(v) { // never executed } </code></pre></pre>chmikes: <pre><p>You are right, but I wanted i to iterate from 0 to len(v) while looping. This is because I&#39;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 &lt; len(v) because of the break instruction. </p> <p>The suggestion to use a found bool would make the code more readable and the intent clearer. I could also have written this</p> <pre><code>itemPos := -1 for i := range v { if v[i].str == ... { // item found at i itemPos = i break } } if itemPos == -1 { // item not found } </code></pre> <p>I also didn&#39;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.</p> <p>Thank you for the help. </p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

430 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传