Skip to content

Iterators: the compiler errors

Level: 201 · anyone whose first for … range over a function did not compile

One line: The compiler names the shape it wants — func must be func(yield func(...) bool) — and then what is wrong with the function it got; yield is an ordinary function, so calling it wrongly gets the ordinary call errors; Seq[int], Seq[string] and Seq2[int, int] are three unrelated types; and a go 1.22 in go.mod turns the feature off whatever toolchain runs the build.

Each transcript below is a driver that writes the broken program, runs go build and prints the compiler's words and the exit status. The wording is go1.25's, recorded so that a later Go that rewords it shows up as drift.

argument is not func

Ranging over a function whose one parameter is an int.

Verified output of iterators_err_not_a_yield_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build not_a_yield.go
# command-line-arguments
./not_a_yield.go:11:17: cannot range over Readings (value of type func(count int)): func must be func(yield func(...) bool): argument is not func
exit status 1

The mistake: Readings(count int) has the right number of parameters and the wrong kind. A range needs a function whose parameter is itself a function — the yield the loop will supply.

The fix:

func Readings(yield func(int) bool) {
    _ = yield(count)
}

yield func does not return bool

The parameter is a function, but it returns nothing, or an error.

Verified output of iterators_err_no_bool_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build no_bool.go
# command-line-arguments
./no_bool.go:11:17: cannot range over Readings (value of type func(handle func(int))): func must be func(yield func(...) bool): yield func does not return bool
exit status 1
$ go build returns_error.go
# command-line-arguments
./returns_error.go:11:17: cannot range over Readings (value of type func(yield func(int) error)): func must be func(yield func(...) bool): yield func does not return bool
exit status 1

The mistake: A callback that cannot say "stop" is not a yield. The bool is how break makes yield return false; a func(int) gives the loop no way to end early, and an error result would have the loop reporting to the producer, which is backwards.

The fix:

func Readings(yield func(int) bool) {
    if !yield(21) {
        return
    }
}

wrong argument count

An iterator with a second parameter, and a constructor that returns an iterator but was not called.

Verified output of iterators_err_wrong_argument_count_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build extra_parameter.go
# command-line-arguments
./extra_parameter.go:11:17: cannot range over Readings (value of type func(yield func(int) bool, limit int)): func must be func(yield func(...) bool): wrong argument count
exit status 1
$ go build not_called.go
# command-line-arguments
./not_called.go:16:17: cannot range over Readings (value of type func() iter.Seq[int]): func must be func(yield func(...) bool): wrong argument count
exit status 1

The mistake: The first program wants limit from somewhere, and a range has only yield to give. The second is the more common slip: Readings returns an iter.Seq[int], and the iterator is Readings(), with the parentheses; the compiler reads Readings itself as a zero-parameter function and counts its arguments. A configuration argument goes on the constructor, and the constructor is what you call.

The fix:

func Readings(limit int) iter.Seq[int] {
    return func(yield func(int) bool) { _ = yield(limit) }
}

for r := range Readings(21) {

unexpected results

An iterator that returns a value.

Verified output of iterators_err_unexpected_results_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build returns_bool.go
# command-line-arguments
./returns_bool.go:11:17: cannot range over Readings (value of type func(yield func(int) bool) bool): func must be func(yield func(...) bool): unexpected results
exit status 1

The mistake: A range statement has nowhere to put a result, so an iterator function returns nothing. What yield returned is the iterator's own business — it tells the iterator to stop, not the loop. An iterator that has something to report after the sequence yields it as a value, or gives the caller a separate method to ask.

The fix:

func Readings(yield func(int) bool) {
    _ = yield(21)
}

too many arguments in call to yield

yield called with two values, then with none — and an iterator whose yield takes three.

Verified output of iterators_err_yield_arity_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build yield_arity.go
# command-line-arguments
./yield_arity.go:7:16: too many arguments in call to yield
    have (number, string)
    want (int)
./yield_arity.go:8:6: not enough arguments in call to yield
    have ()
    want (int)
exit status 1
$ go build three_values.go
# command-line-arguments
./three_values.go:11:17: cannot range over Readings (value of type func(yield func(int, string, bool) bool)): func must be func(yield func(...) bool): yield func has too many parameters
exit status 1

The mistake: Inside the iterator, yield is a plain function parameter with the type the signature gave it, and calling it with the wrong number of values is the ordinary too many arguments / not enough arguments error, with have and want. The third program declares a yield of three parameters; the range statement accepts none, one or two iteration values and nothing beyond (release notes ↗), and the wiki ↗ answers "Why are yield functions limited to at most two arguments?" — a struct carries a third.

The fix:

type Reading struct {
    Sensor string
    Value  int
    Valid  bool
}

func Readings(yield func(Reading) bool) {
    _ = yield(Reading{"hall", 21, true})
}

permits only one iteration variable

Two iteration variables over an iter.Seq[int], and a Seq[int] assigned to a Seq2[int, int].

Verified output of iterators_err_seq_vs_seq2_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build seq_vs_seq2.go
# command-line-arguments
./seq_vs_seq2.go:11:9: range over readings (variable of func type iter.Seq[int]) permits only one iteration variable
./seq_vs_seq2.go:14:36: cannot use readings (variable of func type iter.Seq[int]) as iter.Seq2[int, int] value in variable declaration
exit status 1

The mistake: slices.Values is a Seq, one value per element; the index-and-value form is slices.All. A Seq cannot be used as a Seq2 any more than a func(int) can be used as a func(int, int) — they are different function types, and no conversion exists.

The fix:

for i, r := range slices.All([]int{21, 19, 23}) {

cannot use readings (variable of func type iter.Seq[int]) as iter.Seq[string] value

A Seq[int] passed and assigned where a Seq[string] is wanted.

Verified output of iterators_err_seq_of_wrong_type_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go build wrong_element.go
# command-line-arguments
./wrong_element.go:18:14: cannot use readings (variable of func type iter.Seq[int]) as iter.Seq[string] value in argument to printLabels
./wrong_element.go:19:32: cannot use readings (variable of func type iter.Seq[int]) as iter.Seq[string] value in variable declaration
exit status 1

The mistake: Seq is generic, and each instantiation is its own type, exactly as []int and []string are. There is no covariance to lean on; a sequence of one type becomes a sequence of another through an adapter that converts each element — a Map written the way Filter is in An iterator is lazy.

The fix:

func Map[A, B any](seq iter.Seq[A], f func(A) B) iter.Seq[B] {
    return func(yield func(B) bool) {
        for a := range seq {
            if !yield(f(a)) {
                return
            }
        }
    }
}

printLabels(Map(readings, strconv.Itoa))

requires go1.23 or later

A module whose go.mod says go 1.22, built with go1.25.

Verified output of iterators_err_requires_go123_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

$ go mod init example
$ go mod edit -go=1.22
$ go build .
# example
./main.go:10:17: cannot range over Readings (value of type func(yield func(int) bool)): requires go1.23 or later (-lang was set to go1.22; check go.mod)
exit status 1
$ go mod edit -go=1.23
$ go build .
exit status 0

The mistake: The toolchain sets what the compiler can do; the go line in go.mod sets what the language version is for that module, and range over a function is a Go 1.23 feature. A module that was created before 1.23 and never had its go line raised gets this error on a brand-new toolchain, and the message says where to look: check go.mod. The iter package itself imports fine at go 1.22, which is what the stdversion vet check is for.

The fix:

go mod edit -go=1.23

Sources