-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Closed
Labels
C-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.F-gen_blocks`gen {}` expressions that produce `Iterator`s`gen {}` expressions that produce `Iterator`s
Description
I tried this code:
// edition 2024
#![feature(gen_blocks)]
fn foo() -> impl Iterator {
gen {
yield;
}
}
I expected to see this happen: The gen blocks RFC does not specify the behavior of bare yield
(without an argument). There are three possible choices, each with good reasons for and against:
- Reject it entirely
- Return
None
fromIterator::next()
- Equivalent to
yield ()
Instead, this happened: The current behavior is that of yield ()
.
Meta
rustc --version
:
1.79.0-nightly (2024-04-06 aa1c45908df252a5b0c1)
cc #117078
@rustbot label F-gen_blocks
wmstack
Metadata
Metadata
Assignees
Labels
C-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.F-gen_blocks`gen {}` expressions that produce `Iterator`s`gen {}` expressions that produce `Iterator`s
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
compiler-errors commentedon Apr 8, 2024
I'm not certain why it would make sense to do anything other than
yield ()
. It seems like a total parallel toreturn ()
and I don't think it's particularly beneficial to do anything else.Given that the iterators returned by gen blocks are fused (#122829), it wouldn't make much sense to yield
None
either, since that makesyield
equivalent toreturn
(quit the block and don't come back).We could perhaps "reserve" the behavior by making it error, but seems unnecessarily cautious imo.
Jules-Bertholet commentedon Apr 8, 2024
Arguments for and against each option:
Reject it entirely
Pros:
Cons:
Return
None
fromnext()
Pros:
gen
, which is otherwise impossibleyield /* nothing */
yields nothing, i.e.None
Cons:
break
andreturn
gen
for itEquivalent to
yield ()
Pros:
break
andreturn
Cons:
()
are rareSee also discussion on the RFC thread. Personally I lean towards the "return
None
" option.Jules-Bertholet commentedon Apr 8, 2024
The compiler could detect the presence of a bare
yield
anywhere in the block, and omit theFusedIterator
impl only in that case.compiler-errors commentedon Apr 8, 2024
That's a lot of subtlety (and complexity) for a really niche use case, imo, and I think that the user should just use
impl Iterator<Item = Option<T>>
in that case and explicitly yieldNone
.Jules-Bertholet commentedon Apr 8, 2024
At least for my one use-case today that motivated me to open this issue, that would have been an ergonomic hit.
The use case: I am consuming an API that give me a stream of data. Sometimes there is new data available, sometimes there isn't yet (but might be in the future). My iterator returns
None
fromnext()
in the "no new data" case, enabling me to do stuff like this:The above example could be easily converted to use
impl Iterator<Item = Option<T>>
andwhile let Some(Some(new_data)) = iter.next()
instead. However, an unfused iterator also lets me do the following, which is not so simply adapted:oli-obk commentedon Apr 8, 2024
I think code that tries to be clever should be explicit. And using an unfused iterator to only collect up to the first
None
seems very clever to me.The behavior was not specified in the RFC, because it seemed obvious to me that we want to be consistent with
break
andreturn
. I'll amend the RFC.I'm closing this issue as I don't see T-lang accepting
break
andreturn
fuse
like adaptor.Jules-Bertholet commentedon Apr 9, 2024
Out of curiosity, what would be your preferred "non-clever" version? An iterator over
Option
s combined withtake_while()
?oli-obk commentedon Apr 9, 2024
A function that explicitly turns an unfused iterator into an iterator of iterators so you can just write two nested for loops.
Jules-Bertholet commentedon Apr 9, 2024
That has a lot of hidden complexity. Consider the following:
oli-obk commentedon Apr 9, 2024
Then maybe just handrolling the while let is actually the best way that does not hide any of the unclear things.
Jules-Bertholet commentedon Apr 9, 2024
You mean, handwrite duplicate implementations of
extend
et al (forgoing the performance optimizations of the standard library impls)? I don't see how that's supposed to be an improvement…oli-obk commentedon Apr 9, 2024
I don't know what you mean, and issue format discussions are not a great place to discuss hypotheticals. Also, Aadding special cases to gen blocks won't help you, it just shifts the work to the language/libcore, where we then are unable to make breaking changes. If you have super special requirements, please write a dedicated crate for it, so others can reuse it and upstream optimizations to it.
Jules-Bertholet commentedon Apr 9, 2024
I am asking what you mean when you say "handrolling the while let". I'm not making any proposals—the proposal I did make was rejected, I want to understand your point of view on why you rejected it before I go around making new proposals.
oli-obk commentedon Apr 9, 2024
I presumed #123614 (comment) works for you and you just want a more concise version.
If you want to make a proposal, I would recommend writing a crate that does the job for you and then explaining why there are limitations that are only solved by language or standard library support
Jules-Bertholet commentedon Apr 9, 2024
#123614 (comment) is how I use the un-fused iterator. I don't need or want a more concise version of that sort of code, existing language and stdlib affordances meet my needs just fine. What I want is to be able to use
gen
blocks to produce the non-fused iterators that I later consume in the manner described there.oli-obk commentedon Apr 9, 2024
I don't think we're gonna go out of our way to support unfused iterators. There's no explicit support for them in the standard library functions for them beyond the fact of being sound even in their presence. An iterator is done when it returns
None
the first time.We don't have async blocks that you can poll again after returning a value, even though the Future trait allows for it.
Jules-Bertholet commentedon Apr 9, 2024
std::sync::mspc::TryIter
is explicitly and intentionally unfused.(I agree that GitHub is not the best platform for this discussion, which is why I opened a thread on URLO; feel free to lock this issue)