gogeneratorchannel

Python-style generator implemented as channel: premature reading


I try to implement Python-style generator with a channel following this:

package main

import (
    "fmt"
)

type ContainerIterable[T any] struct {
    content []T
}

func NewContainerIterable[T any]() *ContainerIterable[T] {
    return &ContainerIterable[T]{content: make([]T, 0)}
}

func (c *ContainerIterable[T]) Iterate() chan T {
    ch := make(chan T)
    go func() {
        for _, v := range c.content {
            ch <- v
        }
        close(ch)
    }()
    return ch
}

func (c *ContainerIterable[T]) Add(e T) {
    c.content = append(c.content, e)
}

func main() {
    c := NewContainerIterable[int]()

    for i := 0; i < 5; i++ {
        c.Add(i)
    }

    r := make([]int, 0)
    for v := range c.Iterate() {
        r = append(r, v)
    }

    for i := 0; i < 5; i++ {
        fmt.Println(i, r[i], i == r[i])
    }
}

This works just fine and the output is

0 0 true
1 1 true
2 2 true
3 3 true
4 4 true

However, when I change

r := make([]int, 0)

to

r := make([]int, 5)

the result is different:

0 0 true
1 0 false
2 0 false
3 0 false
4 0 false

I understand that in the second case c.Iterate() starts reading from channel before

    go func() {
        for _, v := range c.content {
            ch <- v
        }

has a chance to send anything to channel. This does not apply to the first case since main() goroutine takes some time to reallocate space for r slice when append() is invoked.

Please advise how to adjust my code to make Iterate() work properly?


Solution

  • This:

    r := make([]int, 5)
    

    will initialize a slice that has 5 elements, all 0. Then you'll add 5 elements more to it. Your program is reading the first 5 zero elements.

    Use: r:=make([]int,0,5)