Per the spec, a "for-range" statement using an iterator requires the iterator's yield function to return bool not an arbitrary (user-defined) boolean:

Range expression                                       1st value                2nd value

array or slice      a  [n]E, *[n]E, or []E             index    i  int          a[i]       E
string              s  string type                     index    i  int          see below  rune
map                 m  map[K]V                         key      k  K            m[k]       V
channel             c  chan E, <-chan E                element  e  E
integer value       n  integer type, or untyped int    value    i  see below
function, 0 values  f  func(func() bool)
function, 1 value   f  func(func(V) bool)              value    v  V
function, 2 values  f  func(func(K, V) bool)           key      k  K            v          V

We should generalize this to any boolean type, similarly to how we allow any string type (not just string) when we range of strings.

Note that the original implementation of the type-checker accepted any boolean type, but the compiler's front-end had a problem with it (#71131). The (temporary) fix for that issue was to adjust the type-checker to match the spec literally. This avoided a compiler panic.

We should change the spec to reflect the original intent, and then revert the fix for #71131.

Comment From: gopherbot

Change https://go.dev/cl/640599 mentions this issue: go/types, types2: require iterator yield to return bool (work-around)

Comment From: gabyhelp

Related Issues

Related Code Changes

Related Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: dolmen

We should generalize this to any boolean type, similarly to how we allow any string type (not just string) when we range of strings.

I'm not convinced by this. Generalizing implies a incompatible change that will affect introspecting at runtime (using reflect) functions that might be iterators.

So could you develop why allowing any bool derived type would be better? What use case does that open?

Comment From: griesemer

The implementation (type checkers, at least) were written from the start to accept any boolean type. The compiler front-end has a bug that causes it to panic when a user-defined boolean is used (#71131). Fixing that bug at this stage of the 1.24 release cycle is risky, so we opted for reporting an error in the type-checker, which is very safe, and which avoids a compiler panic in favor of a very clear error message. We should really fix the compiler front-end (early 1.25 cycle) and then we can also relax this restriction again. I don't know that there are particular issues with reflect at this point since such code didn't work anyway.

Comment From: seankhliao

Is allowing this really good for the ecosystem? these iterators wouldn't be assignable to iter.Seq / iter.Seq2 and would not be composable with any of the iterator helpers out there. It seems strange to allow for covariant result types in this specific corner of the language when its disallowed elsewhere with a faq entry for it.

Comment From: griesemer

@seankhliao That is a good point - I haven't thought about that (I simply opened this issue to document that we changed the original implementation, which allowed for user-defined types, were it not for the bug in the compiler front-end).

We may want to leave things as they are now (TBD).

As an aside, we wouldn't bring in covariance into the language, we'd just allow any boolean type as result type for the specific yield function. But of course, that yield function wouldn't play well with iter.Seq and iter.Seq2.

Comment From: rothelius

To be fair, this is already an issue one level up, with named yield func types: https://go.dev/play/p/3euYnrwXP1P

Comment From: gopherbot

This issue is currently labeled as early-in-cycle for Go 1.25. That time is now, so a friendly reminder to look at it again.

Comment From: rogpeppe

FWIW I agree with @seankhliao's reservations. I'd like to see a solid use case for this before it happens, whatever the intention of the original code.