Skip to content
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

Implement 1581 (FusedIterator) #35656

Merged
merged 1 commit into from
Aug 23, 2016
Merged

Implement 1581 (FusedIterator) #35656

merged 1 commit into from
Aug 23, 2016

Conversation

Stebalien
Copy link
Contributor

@Stebalien Stebalien commented Aug 13, 2016

  • Implement on patterns. See Tracking issue for string patterns #27721 (comment).
  • Handle OS Iterators. A bunch of iterators (Args, Env, etc.) in libstd wrap platform specific iterators. The current ones all appear to be well-behaved but can we assume that future ones will be?
  • Does someone want to audit this? On first glance, all of the iterators on which I implemented FusedIterator appear to be well-behaved but there are a lot of them so a second pair of eyes would be nice.
  • I haven't touched rustc internal iterators (or the internal rand) because rustc doesn't actually call fuse().
  • FusedIterator can't be implemented on std::io::{Bytes, Chars}.

Closes: #35602 (Tracking Issue)
Implements: rust-lang/rfcs#1581

@rust-highfive
Copy link
Collaborator

r? @aturon

(rust_highfive has picked a reviewer for you, use r? to override)

@Stebalien
Copy link
Contributor Author

Please wait for the Travis run to complete before handing off to bors. I haven't done a full bootstrap build yet.

@Stebalien
Copy link
Contributor Author

r? @alexcrichton

@alexcrichton
Copy link
Member

Thanks for the PR @Stebalien! I think it's fine to start conservatively so we don't necessarily need to tag all the iterators today.

I wonder though if this could use a similar trick to zip specialization to get rid of the done bool storage in the structure as well? In theory an already fused iterator doesn't need that boolean.

@Stebalien
Copy link
Contributor Author

I wonder though if this could use a similar trick to zip specialization to get rid of the done bool storage in the structure as well? In theory an already fused iterator doesn't need that boolean.

You can but it makes the implementation a lot messier. See #32999 (comment). However, after thinking about it a bit, it does make the documentation simpler (we can claim "no overhead" without qualifying what we mean by overhead). I've added a commit that does this based on @apasel422's comment (#32999 (comment)).

I think it's fine to start conservatively so we don't necessarily need to tag all the iterators today.

I'm worried that FusedIterator is so invisible that that nobody will even think to implement it. That is, people add implementations of ExactSizeIterator etc. all the time because they're needed to satisfy bounds but, by design, FusedIterator is practically invisible.


#[derive(Clone)]
struct RealFuse<I> {
done: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original suggestion of using

struct RealFuse<I> {
  iter: Option<I>,
}

can save even more space if the underlying iterator contains a NonZero that lets the Option space optimization kick in, but it does have the side effect of changing the time at which the underlying iterator's destructor is called.

Copy link
Contributor Author

@Stebalien Stebalien Aug 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it does have the side effect of changing the time at which the underlying iterator's destructor is called

That's why I switched to this version. The real problem is that the two specializations of Fuse would behave very differently in an unexpected manner.

For example, say you have an iterator iterator holds a lock but you always use it under a Fuse wrapper. Now, someone implements FusedIterator on the underlying iterator. Suddenly, the inner iterator wouldn't be eagerly dropped and your code could deadlock.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense!

@Stebalien
Copy link
Contributor Author

I've addressed your feedback. I can fixup the commits into a logical set of if you want but left them "historically accurate" for review purposes.

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Aug 16, 2016
}

fn spec_next(&mut self) -> Option<<Self::Iter as Iterator>::Item>
where Self::Iter: Iterator {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistically we tend to put the { here on the next line like:

fn foo()
    where // ...
{
    // ...
}

@alexcrichton
Copy link
Member

Looks like the compile failure on travis may be legitimate?

@Stebalien
Copy link
Contributor Author

@alexcrichton done (I don't know if you're notified of commits...).

@alexcrichton
Copy link
Member

Ok, look good to me! Can you add a few tests just as a smoke test as well that .fuse().fuse() doesn't cause an ICE or an assertion to get tripped anywhere?

Other than that with a squash this looks good to me.

#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Fuse<I> {
iter: I,
done: bool
iter: <I as SpecFuse>::T,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that this is going to change Fuse<I> from covariant in I to invariant in I: https://is.gd/btpbYx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not good. Is there any way to fix that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I see how to fix this... Actually, it might make the code cleaner.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be curious to see your approach, as I earlier reported another instance of this issue here: #30642 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh dear (#35727).

Well, I thought that I could do the same thing as the Zip specialization and put the flag in a separate static data field (keeping the iterator in the main struct) but apparently that doesn't appease rust:

struct Fuse<I> {
    iter: I,
    fused: <I as FuseImpl>::Data,
}

There must be some way to do this...

@bors
Copy link
Contributor

bors commented Aug 17, 2016

☔ The latest upstream changes (presumably #35747) made this pull request unmergeable. Please resolve the merge conflicts.

This trait can be used to avoid the overhead of a fuse wrapper when an iterator
is already well-behaved.

Conforming to: RFC 1581
Closes: rust-lang#35602
@Stebalien
Copy link
Contributor Author

So, I've thrown away the associated type specialization (breaking change), squashed, and rebased.

@Stebalien
Copy link
Contributor Author

Note. I've yet to add sanity tests. I'll do that soon (hopefully).

@alexcrichton
Copy link
Member

Hm I would personally be very wary of making the methods of the iterator trait default just yet. Is there any other alternative?

@Stebalien
Copy link
Contributor Author

This makes the methods of the implementation of Iterator on Fuse default. Given that these are both defined in core, coherence (should) disallow further specializations of Iterator on Fuse outside of core.

@alexcrichton
Copy link
Member

Oh whoops! Sorry about that I did indeed misread. @aturon just to confirm, but that's right in that no one else external to the standard library can specialize further here?

@alexcrichton
Copy link
Member

Discussed in @rust-lang/libs triage today and the conclusion was that this is good to go. Thanks @Stebalien!

@bors: r+

@bors
Copy link
Contributor

bors commented Aug 23, 2016

📌 Commit de91872 has been approved by alexcrichton

@Stebalien
Copy link
Contributor Author

@alexcrichton (FYI, I still haven't added the sanity tests yet but thesis is due on 8 days so I'd rather not work on rust-proper until after that deadline).

@bors
Copy link
Contributor

bors commented Aug 23, 2016

⌛ Testing commit de91872 with merge 0bd99f9...

bors added a commit that referenced this pull request Aug 23, 2016
Implement 1581 (FusedIterator)

* [ ] Implement on patterns. See #27721 (comment).
* [ ] Handle OS Iterators. A bunch of iterators (`Args`, `Env`, etc.) in libstd wrap platform specific iterators. The current ones all appear to be well-behaved but can we assume that future ones will be?
* [ ] Does someone want to audit this? On first glance, all of the iterators on which I implemented `FusedIterator` appear to be well-behaved but there are a *lot* of them so a second pair of eyes would be nice.
* I haven't touched rustc internal iterators (or the internal rand) because rustc doesn't actually call `fuse()`.
* `FusedIterator` can't be implemented on `std::io::{Bytes, Chars}`.

Closes: #35602 (Tracking Issue)
Implements: rust-lang/rfcs#1581
@bors bors merged commit de91872 into rust-lang:master Aug 23, 2016
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Jun 3, 2020
Add a test to ensure Fuse stays covariant

When rust-lang#70502 attempted to specialize the data types in `Fuse`, one of the problems we found was that it broke variance. This was also realized when `Fuse` was first added, rust-lang#35656 (diff), but now this PR adds a test so we don't forget again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants