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:
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:
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:
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:
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:
Sources¶
- The Go specification: For statements with range clause ↗ — the three accepted function types.
- Go 1.23 release notes, Changes to the language ↗.
- Package
iter↗ —SeqandSeq2as generic types. - The type checker's wording:
src/internal/types/errors/codes.goat go1.25.5 ↗, the codesInvalidIterVarandInvalidRangeExpr, andsrc/cmd/compile/internal/types2/range.go↗, where "func must be func(yield func(...) bool)" and its suffixes come from. - Go Modules Reference,
godirective ↗ — the language version comes fromgo.mod.