Skip to content

Commit 14cb270

Browse files
committed
[Experiment] do the opposite of what zip side-effect documentation currently guarantees
1 parent cc946fc commit 14cb270

File tree

4 files changed

+33
-220
lines changed

4 files changed

+33
-220
lines changed

library/alloc/src/collections/linked_list.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1925,7 +1925,8 @@ impl<T: Clone> Clone for LinkedList<T> {
19251925
if self.len() > other.len() {
19261926
self.split_off(other.len());
19271927
}
1928-
for (elem, elem_other) in self.iter_mut().zip(&mut iter_other) {
1928+
let len = self.len();
1929+
for (elem, elem_other) in self.iter_mut().zip((&mut iter_other).take(len)) {
19291930
elem.clone_from(elem_other);
19301931
}
19311932
if !iter_other.is_empty() {

library/core/src/iter/adapters/zip.rs

+19-206
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ use crate::iter::{InPlaceIterable, SourceIter, TrustedLen};
1313
pub struct Zip<A, B> {
1414
a: A,
1515
b: B,
16-
// index, len and a_len are only used by the specialized version of zip
17-
index: usize,
18-
len: usize,
19-
a_len: usize,
2016
}
2117
impl<A: Iterator, B: Iterator> Zip<A, B> {
2218
pub(in crate::iter) fn new(a: A, b: B) -> Zip<A, B> {
@@ -88,14 +84,21 @@ where
8884
}
8985

9086
#[inline]
91-
#[doc(hidden)]
92-
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
93-
where
94-
Self: TrustedRandomAccessNoCoerce,
95-
{
96-
// SAFETY: `ZipImpl::__iterator_get_unchecked` has same safety
97-
// requirements as `Iterator::__iterator_get_unchecked`.
98-
unsafe { ZipImpl::get_unchecked(self, idx) }
87+
fn advance_by(&mut self, n: usize) -> Result<(), usize> {
88+
let ((lower_a, _), (lower_b, _)) = (self.a.size_hint(), self.b.size_hint());
89+
let lower = n.min(lower_a).min(lower_b);
90+
let batched = match (self.b.advance_by(lower), self.a.advance_by(lower)) {
91+
(Ok(()), Ok(())) => lower,
92+
_ => panic!("size_hint contract violation"),
93+
};
94+
95+
for i in batched..n {
96+
if let (_, None) | (None, _) = (self.b.next(), self.a.next()) {
97+
return Err(i);
98+
}
99+
}
100+
101+
Ok(())
99102
}
100103
}
101104

@@ -123,30 +126,20 @@ trait ZipImpl<A, B> {
123126
where
124127
A: DoubleEndedIterator + ExactSizeIterator,
125128
B: DoubleEndedIterator + ExactSizeIterator;
126-
// This has the same safety requirements as `Iterator::__iterator_get_unchecked`
127-
unsafe fn get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item
128-
where
129-
Self: Iterator + TrustedRandomAccessNoCoerce;
130129
}
131130

132131
// Work around limitations of specialization, requiring `default` impls to be repeated
133132
// in intermediary impls.
134133
macro_rules! zip_impl_general_defaults {
135134
() => {
136135
default fn new(a: A, b: B) -> Self {
137-
Zip {
138-
a,
139-
b,
140-
index: 0, // unused
141-
len: 0, // unused
142-
a_len: 0, // unused
143-
}
136+
Zip { a, b }
144137
}
145138

146139
#[inline]
147140
default fn next(&mut self) -> Option<(A::Item, B::Item)> {
148-
let x = self.a.next()?;
149141
let y = self.b.next()?;
142+
let x = self.a.next()?;
150143
Some((x, y))
151144
}
152145

@@ -179,8 +172,8 @@ macro_rules! zip_impl_general_defaults {
179172
}
180173
}
181174
}
182-
match (self.a.next_back(), self.b.next_back()) {
183-
(Some(x), Some(y)) => Some((x, y)),
175+
match (self.b.next_back(), self.a.next_back()) {
176+
(Some(y), Some(x)) => Some((x, y)),
184177
(None, None) => None,
185178
_ => unreachable!(),
186179
}
@@ -215,157 +208,6 @@ where
215208

216209
(lower, upper)
217210
}
218-
219-
default unsafe fn get_unchecked(&mut self, _idx: usize) -> <Self as Iterator>::Item
220-
where
221-
Self: TrustedRandomAccessNoCoerce,
222-
{
223-
unreachable!("Always specialized");
224-
}
225-
}
226-
227-
#[doc(hidden)]
228-
impl<A, B> ZipImpl<A, B> for Zip<A, B>
229-
where
230-
A: TrustedRandomAccessNoCoerce + Iterator,
231-
B: TrustedRandomAccessNoCoerce + Iterator,
232-
{
233-
zip_impl_general_defaults! {}
234-
235-
#[inline]
236-
default fn size_hint(&self) -> (usize, Option<usize>) {
237-
let size = cmp::min(self.a.size(), self.b.size());
238-
(size, Some(size))
239-
}
240-
241-
#[inline]
242-
unsafe fn get_unchecked(&mut self, idx: usize) -> <Self as Iterator>::Item {
243-
let idx = self.index + idx;
244-
// SAFETY: the caller must uphold the contract for
245-
// `Iterator::__iterator_get_unchecked`.
246-
unsafe { (self.a.__iterator_get_unchecked(idx), self.b.__iterator_get_unchecked(idx)) }
247-
}
248-
}
249-
250-
#[doc(hidden)]
251-
impl<A, B> ZipImpl<A, B> for Zip<A, B>
252-
where
253-
A: TrustedRandomAccess + Iterator,
254-
B: TrustedRandomAccess + Iterator,
255-
{
256-
fn new(a: A, b: B) -> Self {
257-
let a_len = a.size();
258-
let len = cmp::min(a_len, b.size());
259-
Zip { a, b, index: 0, len, a_len }
260-
}
261-
262-
#[inline]
263-
fn next(&mut self) -> Option<(A::Item, B::Item)> {
264-
if self.index < self.len {
265-
let i = self.index;
266-
// since get_unchecked executes code which can panic we increment the counters beforehand
267-
// so that the same index won't be accessed twice, as required by TrustedRandomAccess
268-
self.index += 1;
269-
// SAFETY: `i` is smaller than `self.len`, thus smaller than `self.a.len()` and `self.b.len()`
270-
unsafe {
271-
Some((self.a.__iterator_get_unchecked(i), self.b.__iterator_get_unchecked(i)))
272-
}
273-
} else if A::MAY_HAVE_SIDE_EFFECT && self.index < self.a_len {
274-
let i = self.index;
275-
// as above, increment before executing code that may panic
276-
self.index += 1;
277-
self.len += 1;
278-
// match the base implementation's potential side effects
279-
// SAFETY: we just checked that `i` < `self.a.len()`
280-
unsafe {
281-
self.a.__iterator_get_unchecked(i);
282-
}
283-
None
284-
} else {
285-
None
286-
}
287-
}
288-
289-
#[inline]
290-
fn size_hint(&self) -> (usize, Option<usize>) {
291-
let len = self.len - self.index;
292-
(len, Some(len))
293-
}
294-
295-
#[inline]
296-
fn nth(&mut self, n: usize) -> Option<Self::Item> {
297-
let delta = cmp::min(n, self.len - self.index);
298-
let end = self.index + delta;
299-
while self.index < end {
300-
let i = self.index;
301-
// since get_unchecked executes code which can panic we increment the counters beforehand
302-
// so that the same index won't be accessed twice, as required by TrustedRandomAccess
303-
self.index += 1;
304-
if A::MAY_HAVE_SIDE_EFFECT {
305-
// SAFETY: the usage of `cmp::min` to calculate `delta`
306-
// ensures that `end` is smaller than or equal to `self.len`,
307-
// so `i` is also smaller than `self.len`.
308-
unsafe {
309-
self.a.__iterator_get_unchecked(i);
310-
}
311-
}
312-
if B::MAY_HAVE_SIDE_EFFECT {
313-
// SAFETY: same as above.
314-
unsafe {
315-
self.b.__iterator_get_unchecked(i);
316-
}
317-
}
318-
}
319-
320-
self.super_nth(n - delta)
321-
}
322-
323-
#[inline]
324-
fn next_back(&mut self) -> Option<(A::Item, B::Item)>
325-
where
326-
A: DoubleEndedIterator + ExactSizeIterator,
327-
B: DoubleEndedIterator + ExactSizeIterator,
328-
{
329-
if A::MAY_HAVE_SIDE_EFFECT || B::MAY_HAVE_SIDE_EFFECT {
330-
let sz_a = self.a.size();
331-
let sz_b = self.b.size();
332-
// Adjust a, b to equal length, make sure that only the first call
333-
// of `next_back` does this, otherwise we will break the restriction
334-
// on calls to `self.next_back()` after calling `get_unchecked()`.
335-
if sz_a != sz_b {
336-
let sz_a = self.a.size();
337-
if A::MAY_HAVE_SIDE_EFFECT && sz_a > self.len {
338-
for _ in 0..sz_a - self.len {
339-
// since next_back() may panic we increment the counters beforehand
340-
// to keep Zip's state in sync with the underlying iterator source
341-
self.a_len -= 1;
342-
self.a.next_back();
343-
}
344-
debug_assert_eq!(self.a_len, self.len);
345-
}
346-
let sz_b = self.b.size();
347-
if B::MAY_HAVE_SIDE_EFFECT && sz_b > self.len {
348-
for _ in 0..sz_b - self.len {
349-
self.b.next_back();
350-
}
351-
}
352-
}
353-
}
354-
if self.index < self.len {
355-
// since get_unchecked executes code which can panic we increment the counters beforehand
356-
// so that the same index won't be accessed twice, as required by TrustedRandomAccess
357-
self.len -= 1;
358-
self.a_len -= 1;
359-
let i = self.len;
360-
// SAFETY: `i` is smaller than the previous value of `self.len`,
361-
// which is also smaller than or equal to `self.a.len()` and `self.b.len()`
362-
unsafe {
363-
Some((self.a.__iterator_get_unchecked(i), self.b.__iterator_get_unchecked(i)))
364-
}
365-
} else {
366-
None
367-
}
368-
}
369211
}
370212

371213
#[stable(feature = "rust1", since = "1.0.0")]
@@ -376,25 +218,6 @@ where
376218
{
377219
}
378220

379-
#[doc(hidden)]
380-
#[unstable(feature = "trusted_random_access", issue = "none")]
381-
unsafe impl<A, B> TrustedRandomAccess for Zip<A, B>
382-
where
383-
A: TrustedRandomAccess,
384-
B: TrustedRandomAccess,
385-
{
386-
}
387-
388-
#[doc(hidden)]
389-
#[unstable(feature = "trusted_random_access", issue = "none")]
390-
unsafe impl<A, B> TrustedRandomAccessNoCoerce for Zip<A, B>
391-
where
392-
A: TrustedRandomAccessNoCoerce,
393-
B: TrustedRandomAccessNoCoerce,
394-
{
395-
const MAY_HAVE_SIDE_EFFECT: bool = A::MAY_HAVE_SIDE_EFFECT || B::MAY_HAVE_SIDE_EFFECT;
396-
}
397-
398221
#[stable(feature = "fused", since = "1.26.0")]
399222
impl<A, B> FusedIterator for Zip<A, B>
400223
where
@@ -448,16 +271,6 @@ impl<A: Debug, B: Debug> ZipFmt<A, B> for Zip<A, B> {
448271
}
449272
}
450273

451-
impl<A: Debug + TrustedRandomAccessNoCoerce, B: Debug + TrustedRandomAccessNoCoerce> ZipFmt<A, B>
452-
for Zip<A, B>
453-
{
454-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455-
// It's *not safe* to call fmt on the contained iterators, since once
456-
// we start iterating they're in strange, potentially unsafe, states.
457-
f.debug_struct("Zip").finish()
458-
}
459-
}
460-
461274
/// An iterator whose items are random-accessible efficiently
462275
///
463276
/// # Safety

library/core/tests/iter/adapters/cloned.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn test_cloned_side_effects() {
3131
.zip(&[1]);
3232
for _ in iter {}
3333
}
34-
assert_eq!(count, 2);
34+
assert_eq!(count, 1);
3535
}
3636

3737
#[test]

library/core/tests/iter/adapters/zip.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn test_zip_next_back_side_effects_exhausted() {
108108
iter.next();
109109
iter.next();
110110
assert_eq!(iter.next_back(), None);
111-
assert_eq!(a, vec![1, 2, 3, 4, 6, 5]);
111+
assert_eq!(a, vec![1, 2, 3, 6, 5, 4]);
112112
assert_eq!(b, vec![200, 300, 400]);
113113
}
114114

@@ -119,7 +119,7 @@ fn test_zip_cloned_sideffectful() {
119119

120120
for _ in xs.iter().cloned().zip(ys.iter().cloned()) {}
121121

122-
assert_eq!(&xs, &[1, 1, 1, 0][..]);
122+
assert_eq!(&xs, &[1, 1, 0, 0][..]);
123123
assert_eq!(&ys, &[1, 1][..]);
124124

125125
let xs = [CountClone::new(), CountClone::new()];
@@ -128,7 +128,7 @@ fn test_zip_cloned_sideffectful() {
128128
for _ in xs.iter().cloned().zip(ys.iter().cloned()) {}
129129

130130
assert_eq!(&xs, &[1, 1][..]);
131-
assert_eq!(&ys, &[1, 1, 0, 0][..]);
131+
assert_eq!(&ys, &[1, 1, 1, 0][..]);
132132
}
133133

134134
#[test]
@@ -138,7 +138,7 @@ fn test_zip_map_sideffectful() {
138138

139139
for _ in xs.iter_mut().map(|x| *x += 1).zip(ys.iter_mut().map(|y| *y += 1)) {}
140140

141-
assert_eq!(&xs, &[1, 1, 1, 1, 1, 0]);
141+
assert_eq!(&xs, &[1, 1, 1, 1, 0, 0]);
142142
assert_eq!(&ys, &[1, 1, 1, 1]);
143143

144144
let mut xs = [0; 4];
@@ -147,7 +147,7 @@ fn test_zip_map_sideffectful() {
147147
for _ in xs.iter_mut().map(|x| *x += 1).zip(ys.iter_mut().map(|y| *y += 1)) {}
148148

149149
assert_eq!(&xs, &[1, 1, 1, 1]);
150-
assert_eq!(&ys, &[1, 1, 1, 1, 0, 0]);
150+
assert_eq!(&ys, &[1, 1, 1, 1, 1, 0]);
151151
}
152152

153153
#[test]
@@ -176,15 +176,15 @@ fn test_zip_map_rev_sideffectful() {
176176

177177
#[test]
178178
fn test_zip_nested_sideffectful() {
179-
let mut xs = [0; 6];
180-
let ys = [0; 4];
179+
let xs = [0; 4];
180+
let mut ys = [0; 6];
181181

182182
{
183183
// test that it has the side effect nested inside enumerate
184-
let it = xs.iter_mut().map(|x| *x = 1).enumerate().zip(&ys);
184+
let it = xs.iter().enumerate().zip(ys.iter_mut().map(|x| *x = 1));
185185
it.count();
186186
}
187-
assert_eq!(&xs, &[1, 1, 1, 1, 1, 0]);
187+
assert_eq!(&ys, &[1, 1, 1, 1, 1, 0]);
188188
}
189189

190190
#[test]
@@ -208,7 +208,7 @@ fn test_zip_nth_back_side_effects_exhausted() {
208208
iter.next();
209209
iter.next();
210210
assert_eq!(iter.nth_back(0), None);
211-
assert_eq!(a, vec![1, 2, 3, 4, 6, 5]);
211+
assert_eq!(a, vec![1, 2, 3, 6, 5, 4]);
212212
assert_eq!(b, vec![200, 300, 400]);
213213
}
214214

@@ -227,8 +227,7 @@ fn test_zip_trusted_random_access_composition() {
227227
assert_eq!(z1.next().unwrap(), (0, 0));
228228

229229
let mut z2 = z1.zip(c);
230-
fn assert_trusted_random_access<T: TrustedRandomAccess>(_a: &T) {}
231-
assert_trusted_random_access(&z2);
230+
232231
assert_eq!(z2.next().unwrap(), ((1, 1), 1));
233232
}
234233

0 commit comments

Comments
 (0)