Skip to content

proposal: iter: Allow nil iterators #68931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
burdiyan opened this issue Aug 18, 2024 · 9 comments
Closed

proposal: iter: Allow nil iterators #68931

burdiyan opened this issue Aug 18, 2024 · 9 comments
Labels
Milestone

Comments

@burdiyan
Copy link

Proposal Details

I'm not sure if this is a bug, or an intended behavior (couldn't find the answer anywhere; looked through package docs, release notes, and GitHub Issues), but as someone who's been using Go for more than a decade, I was really surprised to realize that ranging over nil iterators panics.

E.g. the following code panics:

// This panics:
var it iter.Seq[int]
for n := range it {
    fmt.Println(n)
}

While ranging over nil slices and maps is allowed:

// Doesn't panic:
var it []int
for n := range it {
    fmt.Println(n)
}
// Doesn't panic
var it map[int]int
for n := range it {
    fmt.Println(n)
}

The map example is the thing that brought me to raise this problem. Normally uninitialized maps are not useful anywhere, and accessing them causes panic, but ranging over them is allowed. Seems inconsistent (and frankly a bit unfair) that doing the same with iterators is not allowed.

So, I'd like to propose to allow ranging over nil iterators in the similar way as ranging over nil slices and maps is allowed. I'd assume this is a backwards-compatible change.

@gopherbot gopherbot added this to the Proposal milestone Aug 18, 2024
@gabyhelp
Copy link

Related Issues and Documentation

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

@thepudds
Copy link
Contributor

See #65629 (comment).

@burdiyan
Copy link
Author

Thanks @gabyhelp, @thepudds! Not sure how did I miss the other thread, because I did run a search for various forms of "nil iterator".

Now at least I understand the behavior, although I disagree quite a bit with the decision.

Maybe it would be useful to document this behavior somewhere in the iter package. Even though Russ' argument of "iterators are functions, and calling a nil function panics" makes sense, it's a bit confusing if you think about iterators as some special kind of function, which I assume would be pretty common even for seasoned gophers, at least in the beginning until you realize that there's nothing special about them.

@thepudds
Copy link
Contributor

Hi @burdiyan, maybe it could be more explicit, but it's maybe at least arguably implied by the spec?

From the "For statements with range clause" section (emphasis added):

  1. For a function f, the iteration proceeds by calling f with a new, synthesized yield function as its argument.

and from the "Calls" section:

Calling a nil function value causes a run-time panic.

@earthboundkid
Copy link
Contributor

earthboundkid commented Aug 18, 2024

It's too late to revisit #65629, but package iter could have a None function that yields nothing and a Some or One function that just yields one thing. The alternative is to either DIY, or do slices.Values([]T{}), but using slices.Values is less efficient than a custom type, and DIY is too much effort for a simple, common task.

@burdiyan
Copy link
Author

Thanks @thepudds, the revisited spec is what I missed to read!

Similar to what @earthboundkid is suggesting, I propose adding a few convenience functions to the iter package (similar to the ones bellow), which could help users construct empty iterators that are safe to range over. I ended up doing exactly this in my own iterx package where I have a few other utilities for working with iterators.

// NopSeq is a convenience function
// which returns an empty no-op Seq
// that is safe to range over.
func NopSeq[T any]() Seq[T] {
	return func(func(T) bool) {}
}

// NopSeq2 is the same as NopSeq, but for Seq2.
func NopSeq2[K, V any]() Seq2[K, V] {
	return func(func(K, V) bool) {}
}

The presence of these functions would presumably indicate that ranging over a nil iterator is not safe.

@Merovius
Copy link
Contributor

Merovius commented Aug 18, 2024

The map example is the thing that brought me to raise this problem. Normally uninitialized maps are not useful anywhere, and accessing them causes panic. Seems inconsistent (and frankly a bit unfair) that doing the same with iterators is not allowed.

No, only writes cause a panic, all read-operations are fine (and return the zero value). Arguably, the same is true for nil slices (that's just confusing the matter here, TBH)

To be clear: My point is that you are making an argument-from-consistency. But I disagree with that. Maps and functions and channels and pointers might all be able to be nil, but they all behave very differently and in ways that are very specific to their relative kind of type. For example, range over a nil channel blocks forever, instead of just doing nothing, which is also "inconsistent" with other types. range over a nil pointer panics, which is consistent with func. It's all rather incomparable.

@seankhliao
Copy link
Member

Duplicate of #65629

@seankhliao seankhliao marked this as a duplicate of #65629 Aug 19, 2024
@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale Aug 19, 2024
@earthboundkid
Copy link
Contributor

Made an issue for Nop and friends: #68947.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants