Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I wrote this after finding a bug in some of my code. Basically, I was iterating over a slice of inputs, processing each one. On occasion this processing would reveal new inputs to test, so I appended them to the slice.

    for i := range input {
        if newValue := test(input); newValue != nil {
            input = append(input, newValue)
        }
    }
Of course, this doesn't work, and I figured out (and appreciated) why by reading Go's spec.

Hopefully this brief guide will be of use to someone.



First off, good write up, I'm sure others will find it useful.

To your specific issue, I thought it was good programming etiquette to never modify an object you're iterating over, regardless of how the language handles such a thing. Were my instructors too strict? Is this a common idiom in other environments?


Thank you.

"modify an object" means "change the number of elements", since you obviously want to be able to manipulate the individual elements of a container as you iterate over them. The object here is the container, not the elements themselves.

I can't comment on other languages, but I'd say that guideline is a little too strict for Go.

The classic implementation of Breadth First Search involves iterating over a queue as you fill it.

"don't modify the RHS while in a range clause" would be a more suitable guideline for Go. Note that it's subtley different from "iterating over" - indeed, the answer to my bug was to iterate without using the range clause:

    for i := 0; i < len(input); i++{
        if newValue := test(input); newValue != nil {
            input = append(input, newValue)
        }
    }
I now appreciate the difference between this and the range clause - the length is evaluated every iteration this way. The range clause evaluates it once, at the beginning - rule (1).


After thinking a bit about your examples it makes me appreciate the keyword: the range clause's strictness guarantees iterating only on a certain `range` (self-duh) hence why it's not just called `iterate`.

I found myself making simple mistakes by assuming that range reading on a synchronous channel would cause the goroutine that is sending to the channel to become active. Instead, I wanted to use a for-select statements or a buffered channel because a length guarantee couldn't be made (or so I assume).


The GP's example is equivalent to pushing to the tail of a queue while you're consuming its head, which is a relatively common (and safe) pattern. It looks like the Go designers made this pattern a bit harder to express, in favour of making the general case a bit harder to mess up.


> the Go designers made this pattern a bit harder to express

GP can just do an old-school `for` loop without a `range` clause (generally everyone learns about `for` before learning about `range`) and this immediately becomes incredibly easy to express.


I'm just dabbling in Go now, and this was really helpful. There wasn't a ton of concise writing on this topic that I found. Thanks again.


Thanks for writing this, very informative. You say that adding to a channel's buffer to allow you to append to it while you're reading is code smell. Would you consider just spinning off a goroutine to do the append the same? It seems like it gets around the problem pretty well, though it's pretty much the same thing. Just wondering what you think because I've used this pattern before.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: