the language is special, it gets to use range
.
1func maps() {
2 m := make(map[string]string)
3
4 for key := range m {
5 _ = key
6 }
7
8 for key, value := range m {
9 _, _ = key, value
10 }
11}
1func slices() {
2 s := make([]string, 0)
3
4 for index := range s {
5 _ = index
6 }
7
8 for index, value := range s {
9 _, _ = index, value
10 }
11}
1func channels() {
2 c := make(chan string)
3
4 for value := range c {
5 _ = value
6 }
7}
these are the usually seen ones
No errors, no need to handle any iteration errors.
1func noError() {
2 s := bufio.NewScanner(nil)
3 for s.Scan() {
4 _ = s.Text()
5 }
6}
You keep any errors internally, stopping iteration on error, and providing a way to check iteration errors later. Users may forget to check errors.
1func deferredError() {
2 r := sql.Rows{}
3
4 for r.Next() {
5 _ = r.Scan()
6 }
7 err := r.Err()
8 if err != nil {
9 // handle iteration error
10 _ = err
11 }
12}
You return any iteration errors immediately, forcing users to to use a 3 valued for. Technically the first iteration can also be in the for loop, but then you need to predeclare the error and value var to get the right scope to handle errors.
1func immediateError() {
2 r := tar.NewReader()
3
4 h, err := r.Next()
5 for ; err != nil; h, err = r.Next() {
6 _ = h
7 }
8 if err != nil {
9 // handle error
10 _ = err
11 }
12
13 // alternative
14 var err error
15 var h *tar.Header
16 for h, err = r.Next(); err != nil; h, err = r.Next() {
17 _ = h
18 }
19 // handle error
20 // most likely needs to handle the "no more results" error differently
21 _ = err
22}
You want to be cool and use range
too.
maps and slices need the entire thing present at the start of iteration, but channels....
This does not expose a way to return errors, other than it being part of the iteration value. Also this will leak a goroutine and channel if iteration ever stops early (goroutine holds reference to channel because it is blocked on send, can't be GC'ed).
The leak could be handled by instead returning a read-write channel and catching a write-on-closed channel panic or by passing a done chan to the iterator to signal an end to iteration.
No wonder nobody does this, and people recommend not returning channels as part of public APIs.
1type X struct{}
2func (x X) Iterator() <-chan string {
3 c := make(chan string)
4 go func(){
5 for {
6 // TODO: conditionally beakout
7 c <- generateValue()
8 }
9 close(c)
10 }()
11 return c
12}
13
14func exoticChannelIterator() {
15 var x X
16
17 for value := range x.Iterator() {
18
19 }
20}