Skip to content

Implement Iterator::last for vec::IntoIter #139773

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

Merged
merged 1 commit into from
May 6, 2025

Conversation

thaliaarchi
Copy link
Contributor

Avoid iterating everything when we have random access to the last element.

@rustbot
Copy link
Collaborator

rustbot commented Apr 14, 2025

r? @workingjubilee

rustbot has assigned @workingjubilee.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 14, 2025
@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Apr 14, 2025

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@thaliaarchi
Copy link
Contributor Author

I'm unsatisfied that this suppresses the Clippy diagnostic for vec::IntoIter, as I think it is still good advice. It would be somewhat imprecise for manual last implementations, though, in general.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@samueltardieu
Copy link
Contributor

I'm unsatisfied that this suppresses the Clippy diagnostic for vec::IntoIter, as I think it is still good advice. It would be somewhat imprecise for manual last implementations, though, in general.

This removes the whole purpose of the test which is to check whether this would modify the drop order of the collection's elements. Could you maybe replace the let v = v.into_iter(); by let v = &mut v.into_iter() as &mut dyn DoubleEndedIterator<Item = S>;? This should make the lint trigger, and the drop order be checked.

@thaliaarchi
Copy link
Contributor Author

thaliaarchi commented Apr 14, 2025

I think making a custom DoubleEndedIterator just for the test would be more clear. Really, I just blessed the tests to get CI to pass for the sake of discussion.

Do you think it's fine that this case is no longer linted for vec::IntoIter<T>? On one hand, the forced drop is usually less desirable, but may be exactly what someone wants. Maybe the lint could be selected reenabled for this type and others similar? I could go either way.

@thaliaarchi
Copy link
Contributor Author

thaliaarchi commented Apr 14, 2025

I'm confused why you edited my comment. Perhaps you were grabbing the Markdown contents for a reply and edited it instead? I've reverted the edit and reproduce your comment below:

@samueltardieu commented 2025-04-14T14:02:22Z:

Do you think it's fine that this case is no longer linted for vec::IntoIter<T>? On one hand, the forced drop is usually less desirable, but may be exactly what someone wants. Maybe the lint could be selected reenabled for this type and others similar? I could go either way.

Independently from the Clippy lint, I wonder if your last() implementation conforms to the methods semantics, as described in the documentation of Iterator::last():

Consumes the iterator, returning the last element.

This method will evaluate the iterator until it returns None. While doing so, it keeps track of the current element. After None is returned, last() will then return the last element it saw.

For me, it implies that elements are dropped in order. So I'd say that if your change is accepted (and the documentation for Iterator::last() is updated), then it is fine for Clippy not to lint about Vec::IntoIter<_>::last(), as it would have nothing useful to suggest and to warn about.

@thaliaarchi
Copy link
Contributor Author

I've now made a little wrapper iterator for the test to still exercise that same case, since it wasn't really intending to test the implementation details of vec::IntoIter. And I agree now that vec::IntoIter shouldn't trigger the lint anymore after this change.

@samueltardieu
Copy link
Contributor

samueltardieu commented Apr 15, 2025

I'm confused why you edited my comment. Perhaps you were grabbing the Markdown contents for a reply and edited it instead?

Ooops, this is was not only unintentional but also unnoticed. Thanks for fixing this.

@bors
Copy link
Collaborator

bors commented Apr 23, 2025

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

@workingjubilee
Copy link
Member

workingjubilee commented May 1, 2025

The semantics of Iterator::last are

Consumes the iterator, returning the last element.

Note the "consumes" part.

If I understand this change correctly, on some Vecs (depending on exact size) I can now call last() twice and the second call does not offer None? There's more to the description there that seems to make it very clear the second call should offer None, I think.

@workingjubilee
Copy link
Member

I am not convinced that this implementation is not reneging on Iterator::last's contract. I am willing to hear reasoning otherwise, but I am pretty sure my reading of the docs here is correct.

I think it can probably be fixed to have this obviously-faster implementation while still upholding the equivalent of that contract? But I am not sure. I am not going to think that far right now.

This either

  • needs to be closed
  • needs to be worked on
  • needs to include an explanation of why jubilee doesn't know what she is saying

I will leave it to your judgement to decide which path is viable.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 1, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 1, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@thaliaarchi
Copy link
Contributor Author

I think the detail you're missing is that last takes the iterator by self, not &mut self. That's the “consumes” part. Other places in std use next_back inside of last, so it seems likely to be fine here.

@workingjubilee
Copy link
Member

squint Yep, that's probably why Jubilee doesn't know what she is talking about. Let me take a closer look. I have a question about a particular case involving iterators that have side effects upon iteration that I should just write the test for...

@thaliaarchi
Copy link
Contributor Author

Oh, by the way, if #139847 merges first, I'll add this bit here, though it's entirely inconsequential. It was actually how I discovered this.

--- a/library/std/src/sys/args/common.rs
+++ b/library/std/src/sys/args/common.rs
@@ -51,4 +51,4 @@ impl Iterator for Args {
     #[inline]
-    fn last(mut self) -> Option<OsString> {
-        self.iter.next_back()
+    fn last(self) -> Option<OsString> {
+        self.iter.last()
     }

@thaliaarchi
Copy link
Contributor Author

thaliaarchi commented May 1, 2025

The drop order of Vec and of vec::IntoIter are the same, since they both use ptr::drop_in_place on their slices (though neither documents this). ptr::drop_in_place drops the elements of a slice from front-to-back, which matches the front-to-back iteration of last. Since Iterator::last takes the iterator by self, the iterator is dropped before the last element is returned and the drop order is always the same.

This is the current status quo:

struct D(u32);
impl Drop for D {
    fn drop(&mut self) {
        println!("drop {}", self.0);
    }
}

println!("Vec::drop");
drop(vec![D(1), D(2), D(3)]);
println!("vec::IntoIter::last");
drop(vec![D(4), D(5), D(6)].into_iter().last());
Vec::drop
drop 1
drop 2
drop 3
vec::IntoIter::last
drop 4
drop 5
drop 6

@workingjubilee
Copy link
Member

still cogitating but first

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 2, 2025
@thaliaarchi thaliaarchi force-pushed the vec-into-iter-last branch from e9fd1b2 to 23c14a5 Compare May 3, 2025 03:08
@thaliaarchi thaliaarchi force-pushed the vec-into-iter-last branch from 23c14a5 to cbdd713 Compare May 3, 2025 03:08
@thaliaarchi
Copy link
Contributor Author

I've applied the patch I mentioned, now that #139847 has merged.

@workingjubilee
Copy link
Member

I... feel very skeptical of the standard library's use of specialization, now, as it contributed greatly to my initial uncertainty. I feel like it weakens the ability to do generic reasoning.

However, I am, for now, reasonably confident that it does not affect this PR.

@bors r+

@bors
Copy link
Collaborator

bors commented May 5, 2025

📌 Commit cbdd713 has been approved by workingjubilee

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 5, 2025
Zalathar added a commit to Zalathar/rust that referenced this pull request May 6, 2025
…workingjubilee

Implement `Iterator::last` for `vec::IntoIter`

Avoid iterating everything when we have random access to the last element.
bors added a commit to rust-lang-ci/rust that referenced this pull request May 6, 2025
Rollup of 12 pull requests

Successful merges:

 - rust-lang#139550 (Fix `-Zremap-path-scope` rmeta handling)
 - rust-lang#139773 (Implement `Iterator::last` for `vec::IntoIter`)
 - rust-lang#140035 (Implement RFC 3503: frontmatters)
 - rust-lang#140176 (Fix linking statics on Arm64EC)
 - rust-lang#140251 (coverage-dump: Resolve global file IDs to filenames)
 - rust-lang#140393 (std: get rid of `sys_common::process`)
 - rust-lang#140532 (Fix RustAnalyzer discovery of rustc's `stable_mir` crate)
 - rust-lang#140598 (Steer docs to `utf8_chunks` and `Iterator::take`)
 - rust-lang#140634 (Use more accurate ELF flags on MIPS)
 - rust-lang#140673 (Clean rustdoc tests folder)
 - rust-lang#140678 (Be a bit more relaxed about not yet constrained infer vars in closure upvar analysis)
 - rust-lang#140687 (Update mdbook to 0.4.49)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request May 6, 2025
Rollup of 12 pull requests

Successful merges:

 - rust-lang#139550 (Fix `-Zremap-path-scope` rmeta handling)
 - rust-lang#139764 (Consistent trait bounds for ExtractIf Debug impls)
 - rust-lang#139773 (Implement `Iterator::last` for `vec::IntoIter`)
 - rust-lang#140035 (Implement RFC 3503: frontmatters)
 - rust-lang#140251 (coverage-dump: Resolve global file IDs to filenames)
 - rust-lang#140393 (std: get rid of `sys_common::process`)
 - rust-lang#140532 (Fix RustAnalyzer discovery of rustc's `stable_mir` crate)
 - rust-lang#140598 (Steer docs to `utf8_chunks` and `Iterator::take`)
 - rust-lang#140634 (Use more accurate ELF flags on MIPS)
 - rust-lang#140673 (Clean rustdoc tests folder)
 - rust-lang#140678 (Be a bit more relaxed about not yet constrained infer vars in closure upvar analysis)
 - rust-lang#140687 (Update mdbook to 0.4.49)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit d5c09b4 into rust-lang:master May 6, 2025
6 checks passed
@rustbot rustbot added this to the 1.88.0 milestone May 6, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request May 6, 2025
Rollup merge of rust-lang#139773 - thaliaarchi:vec-into-iter-last, r=workingjubilee

Implement `Iterator::last` for `vec::IntoIter`

Avoid iterating everything when we have random access to the last element.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library 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