1+ //! Inplace iterate-and-collect specialization for `Vec`
2+ //!
3+ //! The specialization in this module applies to iterators in the shape of
4+ //! `source.adapter().adapter().adapter().collect::<Vec<U>>()`
5+ //! where `source` is an owning iterator obtained from [`Vec<T>`], [`Box<[T]>`] (by conversion to `Vec`)
6+ //! or [`BinaryHeap<T>`], the adapters each consume one or more items per step
7+ //! (represented by [`InPlaceIterable`]), provide transitive access to `source` (via [`SourceIter`])
8+ //! and thus the underlying allocation. And finally the layouts of `T` and `U` must
9+ //! have the same size and alignment, this is currently ensured via const eval instead of trait
10+ //! bounds.
11+ //!
12+ //! [`BinaryHeap<T>`]: crate::collections::BinaryHeap
13+ //! [`Box<[T]>`]: crate::boxed::Box
14+ //!
15+ //! By extension some other collections which use `collect::Vec<_>()` internally in their
16+ //! `FromIterator` implementation benefit from this too.
17+ //!
18+ //! Access to the underlying source goes through a further layer of indirection via the private
19+ //! trait [`AsIntoIter`] to hide the implementation detail that other collections may use
20+ //! `vec::IntoIter` internally.
21+ //!
22+ //! In-place iteration depends on the interaction of several unsafe traits, implementation
23+ //! details of multiple parts in the iterator pipeline and often requires holistic reasoning
24+ //! across multiple structs since iterators are executed cooperatively rather than having
25+ //! a central evaluator/visitor struct executing all iterator components.
26+ //!
27+ //! # Reading from and writing to the same allocation
28+ //!
29+ //! By its nature collecting in place means that the reader and writer side of the iterator
30+ //! use the same allocation. Since `fold()` and co. take a reference to the iterator for the
31+ //! duration of the iteration that means we can't interleave the step of reading a value
32+ //! and getting a reference to write to. Instead raw pointers must be used on the reader
33+ //! and writer side.
34+ //!
35+ //! That writes never clobber a yet-to-be-read item is ensured by the [`InPlaceIterable`] requirements.
36+ //!
37+ //! # Layout constraints
38+ //!
39+ //! [`Allocator`] requires that `allocate()` and `deallocate()` have matching alignment and size.
40+ //! Additionally this specialization doesn't make sense for ZSTs as there is no reallocation to
41+ //! avoid and it would make pointer arithmetic more difficult.
42+ //!
43+ //! [`Allocator`]: core::alloc::Allocator
44+ //!
45+ //! # Drop- and panic-safety
46+ //!
47+ //! Iteration can panic, requiring dropping the already written parts but also the remainder of
48+ //! the source. Iteration can also leave some source items unconsumed which must be dropped.
49+ //! All those drops in turn can panic which then must either leak the allocation or abort to avoid
50+ //! double-drops.
51+ //!
52+ //! These tasks are handled by [`InPlaceDrop`] and [`vec::IntoIter::forget_allocation_drop_remaining()`]
53+ //!
54+ //! [`vec::IntoIter::forget_allocation_drop_remaining()`]: super::IntoIter::forget_allocation_drop_remaining()
55+ //!
56+ //! # O(1) collect
57+ //!
58+ //! The main iteration itself is further specialized when the iterator implements
59+ //! [`TrustedRandomAccessNoCoerce`] to let the optimizer see that it is a counted loop with a single
60+ //! induction variable. This can turn some iterators into a noop, i.e. it reduces them from O(n) to
61+ //! O(1). This particular optimization is quite fickle and doesn't always work, see [#79308]
62+ //!
63+ //! [#79308]: https://github.com/rust-lang/rust/issues/79308
64+ //!
65+ //! Since unchecked accesses through that trait do not advance the read pointer of `IntoIter`
66+ //! this would interact unsoundly with the requirements about dropping the tail described above.
67+ //! But since the normal `Drop` implementation of `IntoIter` would suffer from the same problem it
68+ //! is only correct for `TrustedRandomAccessNoCoerce` to be implemented when the items don't
69+ //! have a destructor. Thus that implicit requirement also makes the specialization safe to use for
70+ //! in-place collection.
71+ //!
72+ //! # Adapter implementations
73+ //!
74+ //! The invariants for adapters are documented in [`SourceIter`] and [`InPlaceIterable`], but
75+ //! getting them right can be rather subtle for multiple, sometimes non-local reasons.
76+ //! For example `InPlaceIterable` would be valid to implement for [`Peekable`], except
77+ //! that it is stateful, cloneable and `IntoIter`'s clone implementation shortens the underlying
78+ //! allocation which means if the iterator has been peeked and then gets cloned there no longer is
79+ //! enough room, thus breaking an invariant (#85322).
80+ //!
81+ //! [#85322]: https://github.com/rust-lang/rust/issues/85322
82+ //! [`Peekable`]: core::iter::Peekable
83+ //!
84+ //!
85+ //! # Examples
86+ //!
87+ //! Some cases that are optimized by this specialization, more can be found in the `Vec`
88+ //! benchmarks:
89+ //!
90+ //! ```rust
91+ //! # #[allow(dead_code)]
92+ //! /// Converts a usize vec into an isize one.
93+ //! pub fn cast(vec: Vec<usize>) -> Vec<isize> {
94+ //! // Does not allocate, free or panic. On optlevel>=2 it does not loop.
95+ //! // Of course this particular case could and should be written with `into_raw_parts` and
96+ //! // `from_raw_parts` instead.
97+ //! vec.into_iter().map(|u| u as isize).collect()
98+ //! }
99+ //! ```
100+ //!
101+ //! ```rust
102+ //! # #[allow(dead_code)]
103+ //! /// Drops remaining items in `src` and if the layouts of `T` and `U` match it
104+ //! /// returns an empty Vec backed by the original allocation. Otherwise it returns a new
105+ //! /// empty vec.
106+ //! pub fn recycle_allocation<T, U>(src: Vec<T>) -> Vec<U> {
107+ //! src.into_iter().filter_map(|_| None).collect()
108+ //! }
109+ //! ```
110+ //!
111+ //! ```rust
112+ //! let vec = vec![13usize; 1024];
113+ //! let _ = vec.into_iter()
114+ //! .enumerate()
115+ //! .filter_map(|(idx, val)| if idx % 2 == 0 { Some(val+idx) } else {None})
116+ //! .collect::<Vec<_>>();
117+ //!
118+ //! // is equivalent to the following, but doesn't require bounds checks
119+ //!
120+ //! let mut vec = vec![13usize; 1024];
121+ //! let mut write_idx = 0;
122+ //! for idx in 0..vec.len() {
123+ //! if idx % 2 == 0 {
124+ //! vec[write_idx] = vec[idx] + idx;
125+ //! write_idx += 1;
126+ //! }
127+ //! }
128+ //! vec.truncate(write_idx);
129+ //! ```
1130use core:: iter:: { InPlaceIterable , SourceIter , TrustedRandomAccessNoCoerce } ;
2131use core:: mem:: { self , ManuallyDrop } ;
3132use core:: ptr:: { self } ;
@@ -16,11 +145,8 @@ where
16145 I : Iterator < Item = T > + SourceIter < Source : AsIntoIter > + InPlaceIterableMarker ,
17146{
18147 default fn from_iter ( mut iterator : I ) -> Self {
19- // Additional requirements which cannot expressed via trait bounds. We rely on const eval
20- // instead:
21- // a) no ZSTs as there would be no allocation to reuse and pointer arithmetic would panic
22- // b) size match as required by Alloc contract
23- // c) alignments match as required by Alloc contract
148+ // See "Layout constraints" section in the module documentation. We rely on const
149+ // optimization here since these conditions currently cannot be expressed as trait bounds
24150 if mem:: size_of :: < T > ( ) == 0
25151 || mem:: size_of :: < T > ( )
26152 != mem:: size_of :: < <<I as SourceIter >:: Source as AsIntoIter >:: Item > ( )
@@ -58,21 +184,13 @@ where
58184 ) ;
59185 }
60186
61- // drop any remaining values at the tail of the source
62- // but prevent drop of the allocation itself once IntoIter goes out of scope
63- // if the drop panics then we also leak any elements collected into dst_buf
187+ // Drop any remaining values at the tail of the source but prevent drop of the allocation
188+ // itself once IntoIter goes out of scope.
189+ // If the drop panics then we also leak any elements collected into dst_buf.
64190 //
65- // FIXME: Since `SpecInPlaceCollect::collect_in_place` above might use
66- // `__iterator_get_unchecked` internally, this call might be operating on
67- // a `vec::IntoIter` with incorrect internal state regarding which elements
68- // have already been “consumed”. However, the `TrustedRandomIteratorNoCoerce`
69- // implementation of `vec::IntoIter` is only present if the `Vec` elements
70- // don’t have a destructor, so it doesn’t matter if elements are “dropped multiple times”
71- // in this case.
72- // This argument technically currently lacks justification from the `# Safety` docs for
73- // `SourceIter`/`InPlaceIterable` and/or `TrustedRandomAccess`, so it might be possible that
74- // someone could inadvertently create new library unsoundness
75- // involving this `.forget_allocation_drop_remaining()` call.
191+ // Note: This access to the source wouldn't be allowed by the TrustedRandomIteratorNoCoerce
192+ // contract (used by SpecInPlaceCollect below). But see the "O(1) collect" section in the
193+ // module documenttation why this is ok anyway.
76194 src. forget_allocation_drop_remaining ( ) ;
77195
78196 let vec = unsafe { Vec :: from_raw_parts ( dst_buf, len, cap) } ;
@@ -155,7 +273,21 @@ where
155273 }
156274}
157275
158- // internal helper trait for in-place iteration specialization.
276+ /// Internal helper trait for in-place iteration specialization.
277+ ///
278+ /// Currently this is only implemented by [`vec::IntoIter`] - returning a reference to itself - and
279+ /// [`binary_heap::IntoIter`] which returns a reference to its inner representation.
280+ ///
281+ /// Since this is an internal trait it hides the implementation detail `binary_heap::IntoIter`
282+ /// uses `vec::IntoIter` internally.
283+ ///
284+ /// [`vec::IntoIter`]: super::IntoIter
285+ /// [`binary_heap::IntoIter`]: crate::collections::binary_heap::IntoIter
286+ ///
287+ /// # Safety
288+ ///
289+ /// In-place iteration relies on implementation details of `vec::IntoIter`, most importantly that
290+ /// it does not create references to the whole allocation during iteration, only raw pointers
159291#[ rustc_specialization_trait]
160292pub ( crate ) unsafe trait AsIntoIter {
161293 type Item ;
0 commit comments