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
+ //! ```
1
130
use core:: iter:: { InPlaceIterable , SourceIter , TrustedRandomAccessNoCoerce } ;
2
131
use core:: mem:: { self , ManuallyDrop } ;
3
132
use core:: ptr:: { self } ;
@@ -16,11 +145,8 @@ where
16
145
I : Iterator < Item = T > + SourceIter < Source : AsIntoIter > + InPlaceIterableMarker ,
17
146
{
18
147
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
24
150
if mem:: size_of :: < T > ( ) == 0
25
151
|| mem:: size_of :: < T > ( )
26
152
!= mem:: size_of :: < <<I as SourceIter >:: Source as AsIntoIter >:: Item > ( )
@@ -58,21 +184,13 @@ where
58
184
) ;
59
185
}
60
186
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.
64
190
//
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.
76
194
src. forget_allocation_drop_remaining ( ) ;
77
195
78
196
let vec = unsafe { Vec :: from_raw_parts ( dst_buf, len, cap) } ;
@@ -155,7 +273,21 @@ where
155
273
}
156
274
}
157
275
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
159
291
#[ rustc_specialization_trait]
160
292
pub ( crate ) unsafe trait AsIntoIter {
161
293
type Item ;
0 commit comments