-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
impl<T> IntoIterator for [T; $N] #32871
Conversation
This allows an array to move its values out through iteration. I find this especially handy for `flat_map`, to expand one item into several without having to allocate a `Vec`, like one of the new tests: fn test_iterator_flat_map() { assert!((0..5).flat_map(|i| [2 * i, 2 * i + 1]).eq(0..10)); } Note the array must be moved for the iterator to own it, so you probably don't want this for large `T` or very many items. But for small arrays, it should be faster than bothering with a vector and the heap.
r? @brson (rust_highfive has picked a reviewer for you, use r? to override) |
In case it's questioned, I don't know why the Also, I'd love a more direct way than |
Hmm, I had only run libcoretest, sorry. The CI failure is from rustdoc, in a snippet like this:
Before it got something like Now it gets to my new So, I suppose my addition is a breaking change. There are easy ways around it, in this case either change to |
Now some iter doctests are failing, all cases of array So... I'll await a decision whether this break is even tenable before I try to fix any more, however trivial. |
cc @rust-lang/libs and #25725. Being able to move out of fixed sized arrays would definitely be nice. However, the libs team has not been keen on adding functionality to fixed sized arrays via the up-to-32 macro hack we have now, preferring to wait until we have a more structured way to handle all fixed sized arrays. (Also, being a breaking change is unfortunate, although, in its favour, it is likely considered a "minor change" per the API evolution RFC.) |
Thanks. FWIW, the iterator itself is not in the macro hack here, just the initial |
let ndrop = cmp::min(n, len); | ||
let slice = array.as_mut_slice(); | ||
for p in &mut slice[self.index..self.index + ndrop] { | ||
unsafe { ptr::drop_in_place(p); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if one of these destructors panics? self.index
probably needs to be updated either before the loop ("leak amplification") or inside the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point! I think I prefer inside the loop, just to leak as little as possible, but I could go either way.
Yeah, I agree that the The existing specialisation scheme probably isn't quite enough, as I suspect we'd need the lattice version that allows handling overlapping blanket implementations (the impl for |
} | ||
|
||
// Prevent the array as a whole from dropping. | ||
unsafe { ptr::write(&mut self.array, None); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually has the same hazard you raised for nth
-- if there's a panic in one of the drop_in_place
calls, we won't get to clobber self.array
. And AFAICT a panic in a drop function will still try to drop the struct members, or even locals if I were to take()
the array out early.
I think this will need drop-prevention to be an additional wrapper on the array, to be unwound on its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrayvec has experienced this hazard too, and it uses two drop flags for this reason (nested types).
I'll raise the one issue I know with Option (and the reason this doesn't use Option). In |
@bluss interesting - so the concern is that once the 0th element is moved (or dropped in place), then that memory is sort of undefined, and the tag of the |
I feel less concerned about |
In arrayvec it was a practical concern, since it used uninitialized or bitwise zeroed elements when empty. Something that uses unsafe_no_drop_flag and sets itself to 0 even though it's "NonZero" would in fact be problematic here when dropped in place. (This is what zeroing drop used to do until it became filling drop). |
Thanks for the PR @cuviper! The libs team discussed this during triage yesterday and out conclusion was that we don't want to merge this for now. This unfortunately needs to mention the type in the iterator rather than just the As a result this seems like something which would perhaps want to bake on crates.io first, so we decided to close this for now. We also thought that if |
That's a good point that the type signature would want to change between this and the ideal integer generics! So I understand holding off for now. I do have safety fixes for the issues raised so far, which I'll go push to my branch later just to have available. When integer generics finally come around, hopefully we can get back on this quickly. :) I'll see about publishing something in a crate too. Is your |
|
I see, thanks! |
Ah yeah sorry, the jargon is pretty cryptic :( |
This allows an array to move its values out through iteration. I find this especially handy for flat_map, to expand one item into several without having to allocate a Vec, like the new test: ```rust fn test_iter_flat_map() { assert!((0..5).flat_map(|i| arr![i32; 2 * i, 2 * i + 1]).eq(0..10)); } ``` This is a spiritual successor to rust-lang/rust#32871, which was declined at least until it could use integer generics. GenericArray fills that gap, though, and could use IntoIterator today.
FWIW, I have rebased and updated my branch (comparison), and it should be simple to change it to |
@cuviper: Nice. But wow, those .into_iter() usages in the examples are worrying. |
I'm tempted to go ahead and submit those |
With RFC 2000 on the horizon, do you think we could try this again? We would still have to start with the less-satisfactory
I want to put this together now, at least. |
impl IntoIterator for arrays This allows an array to move its values out through iteration. This was attempted once before in #32871, but closed because the `IntoIter<T, [T; $N]>` type is not something we would want to stabilize. However, RFC 2000's const generics (#44580) are now on the horizon, so we can plan on changing this to `IntoIter<T, const N: usize>` before stabilization. Adding the `impl IntoIterator` now will allows folks to go ahead and iterate arrays in stable code. They just won't be able to name the `array::IntoIter` type or use its inherent `as_slice`/`as_mut_slice` methods until they've stabilized. Quite a few iterator examples were already using `.into_iter()` on arrays, getting auto-deref'ed to the slice iterator. These were easily fixed by calling `.iter()` instead, but it shows that this might cause a lot of breaking changes in the wild, and we'll need a crater run to evaluate this. Outside of examples, there was only one instance of in-tree code that had a problem. Fixes #25725. r? @alexcrichton
@alexcrichton Re-reading this again, I wonder why do you think this is a problem? When const generics arrive, the |
@RReverser we didn't want to commit to that type definition at the time. The type definition will want to change with const generics, and it didn't seem pressing enough to take the future ergonomic hit at the time. Times change though! (also this PR is over 2 years old, naturally it's pretty far out of cache) |
My newer PR was #49000, where I still wasn't able to update the type definition to const generics, but my hope was that we could just leave that part unstable for the time being. We could have usable array iterators, just not be able to name the iterator type in stable code. We're now blocked on having a good migration story to fix conflicting "legacy" use of |
This allows an array to move its values out through iteration. I find
this especially handy for
flat_map
, to expand one item into severalwithout having to allocate a
Vec
, like one of the new tests:Note the array must be moved for the iterator to own it, so you probably
don't want this for large
T
or very many items. But for small arrays,it should be faster than bothering with a vector and the heap.