Skip to content

Commit c41e97d

Browse files
committed
Auto merge of #156654 - AngelicosPhosphoros:angelicos_phosphoros/improve_range_inclusive_fold, r=<try>
Call visitor only in one place in bodies of folds for RangeInclusive
2 parents e96c36b + 3830c04 commit c41e97d

3 files changed

Lines changed: 148 additions & 22 deletions

File tree

library/core/src/iter/range.rs

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,18 +1325,33 @@ impl<T: TrustedStep> RangeInclusiveIteratorImpl for ops::RangeInclusive<T> {
13251325
}
13261326

13271327
let mut accum = init;
1328+
let mut i: T = self.start;
1329+
// We use loop with postcondition because
1330+
// it allows us to finish loop even when `self.end` is `T::MAX`.
1331+
// Precondition is checked before loop at the beginning of the function.
1332+
loop {
1333+
let current = i;
1334+
let is_last: bool = i == self.end;
1335+
1336+
// Need to update both fields before calling function
1337+
// because we can eagerly exit due to panic or ? operator.
1338+
1339+
// Writing to `self.exhausted` at every iteration is cheaper than branching.
1340+
// Especially because we need to write neighbouring field _anyway_.
1341+
self.exhausted = is_last;
1342+
// This should compile to conditional move for simple types.
1343+
self.start = if is_last {
1344+
current
1345+
} else {
1346+
// SAFETY: just checked precondition
1347+
i = unsafe { Step::forward_unchecked(i, 1) };
1348+
i
1349+
};
13281350

1329-
while self.start < self.end {
1330-
// SAFETY: just checked precondition
1331-
let n = unsafe { Step::forward_unchecked(self.start, 1) };
1332-
let n = mem::replace(&mut self.start, n);
1333-
accum = f(accum, n)?;
1334-
}
1335-
1336-
self.exhausted = true;
1337-
1338-
if self.start == self.end {
1339-
accum = f(accum, self.start)?;
1351+
accum = f(accum, current)?;
1352+
if is_last {
1353+
break;
1354+
}
13401355
}
13411356

13421357
try { accum }
@@ -1365,23 +1380,33 @@ impl<T: TrustedStep> RangeInclusiveIteratorImpl for ops::RangeInclusive<T> {
13651380
F: FnMut(B, T) -> R,
13661381
R: Try<Output = B>,
13671382
{
1383+
// Code of this implementation is almost the same as `spec_try_fold`
1384+
// except it updates self.end and decrements counter.
1385+
// See it for comments for justification of code structure
1386+
13681387
if self.is_empty() {
13691388
return try { init };
13701389
}
13711390

13721391
let mut accum = init;
1392+
let mut i: T = self.end;
1393+
loop {
1394+
let current = i;
1395+
let is_last: bool = i == self.start;
1396+
1397+
self.exhausted = is_last;
1398+
self.end = if is_last {
1399+
current
1400+
} else {
1401+
// SAFETY: just checked precondition
1402+
i = unsafe { Step::backward_unchecked(i, 1) };
1403+
i
1404+
};
13731405

1374-
while self.start < self.end {
1375-
// SAFETY: just checked precondition
1376-
let n = unsafe { Step::backward_unchecked(self.end, 1) };
1377-
let n = mem::replace(&mut self.end, n);
1378-
accum = f(accum, n)?;
1379-
}
1380-
1381-
self.exhausted = true;
1382-
1383-
if self.start == self.end {
1384-
accum = f(accum, self.start)?;
1406+
accum = f(accum, current)?;
1407+
if is_last {
1408+
break;
1409+
}
13851410
}
13861411

13871412
try { accum }

library/coretests/tests/ascii_char.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,46 @@ fn test_extend() {
3838
s.extend(Char::CapitalA..=Char::CapitalC);
3939
assert_eq!(s, String::from("abcABC"));
4040
}
41+
42+
#[test]
43+
fn test_range_inclusive_try_fold() {
44+
// Visit every value once.
45+
let mut it = Char::MIN..=Char::MAX;
46+
let mut expected: u32 = 0;
47+
it.try_fold((), |_, x| {
48+
assert!(expected <= u32::from(Char::MAX));
49+
assert_eq!(expected, u32::from(x));
50+
expected += 1;
51+
Some(())
52+
});
53+
54+
let mut it = Char::MIN..=Char::MAX;
55+
it.try_fold((), |_, x| (x < Char::CapitalA).then_some(()));
56+
assert!(!it.is_empty());
57+
assert_eq!(it.next(), Some(Char::CapitalB));
58+
59+
let mut it = Char::MIN..=Char::MAX;
60+
it.try_fold((), |_, x| (x != Char::MAX).then_some(()));
61+
assert!(it.is_empty());
62+
assert_eq!(it.next(), None);
63+
64+
// Visit every value once.
65+
let mut it = Char::MIN..=Char::MAX;
66+
let mut expected: i32 = u8::from(Char::MAX).into();
67+
it.try_rfold((), |_, x| {
68+
assert!(expected >= 0);
69+
assert_eq!(expected, i32::from(u8::from(x)));
70+
expected -= 1;
71+
Some(())
72+
});
73+
74+
let mut it = Char::MIN..=Char::MAX;
75+
it.try_rfold((), |_, x| (x > Char::CapitalB).then_some(()));
76+
assert!(!it.is_empty());
77+
assert_eq!(it.next_back(), Some(Char::CapitalA));
78+
79+
let mut it = Char::MIN..=Char::MAX;
80+
it.try_rfold((), |_, x| (x != Char::MIN).then_some(()));
81+
assert!(it.is_empty());
82+
assert_eq!(it.next(), None);
83+
}

library/coretests/tests/iter/range.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,64 @@ fn test_range_inclusive_folds() {
422422
assert!(it.is_empty());
423423
assert_eq!(it.try_rfold(0, |a, b| Some(a + b)), Some(0));
424424
assert!(it.is_empty());
425+
426+
// No endless loop.
427+
let mut it = 0u8..=u8::MAX;
428+
assert_eq!(it.try_fold(0u32, |a, b| Some(a + u32::from(b))), Some(32640));
429+
assert!(it.is_empty());
430+
assert_eq!(it.try_fold(0, |a, b| Some(a + b)), Some(0));
431+
assert!(it.is_empty());
432+
433+
// No endless loop.
434+
let mut it = i8::MIN..=i8::MAX;
435+
assert_eq!(it.try_rfold(0i32, |a, b| Some(a + i32::from(b))), Some(-128));
436+
assert!(it.is_empty());
437+
assert_eq!(it.try_rfold(0, |a, b| Some(a + b)), Some(0));
438+
assert!(it.is_empty());
439+
440+
// Visit every value exactly once in order.
441+
let mut expected: Option<u8> = Some(0);
442+
let mut it = 0u8..=u8::MAX;
443+
it.try_fold((), |_, x| {
444+
assert!(expected.is_some());
445+
assert_eq!(Some(x), expected);
446+
expected = expected.and_then(|e| e.checked_add(1));
447+
Some(())
448+
});
449+
450+
// Visit every value exactly once in reverse order.
451+
let mut expected: Option<u8> = Some(u8::MAX);
452+
let mut it = 0u8..=u8::MAX;
453+
it.try_rfold((), |_, x| {
454+
assert!(expected.is_some());
455+
assert_eq!(Some(x), expected);
456+
expected = expected.and_then(|e| e.checked_sub(1));
457+
Some(())
458+
});
459+
460+
// Early stop updates state correctly.
461+
let mut it = 0..=100;
462+
it.try_fold((), |_, x| (x < 10).then_some(()));
463+
assert!(!it.is_empty());
464+
assert_eq!(it.next(), Some(11));
465+
466+
// Early stop updates state correctly at the end.
467+
let mut it = 0..=100;
468+
it.try_fold((), |_, x| (x < 100).then_some(()));
469+
assert!(it.is_empty());
470+
assert_eq!(it.next(), None);
471+
472+
// Early stop updates state correctly.
473+
let mut it = 0..=100;
474+
it.try_rfold((), |_, x| (x > 10).then_some(()));
475+
assert!(!it.is_empty());
476+
assert_eq!(it.next_back(), Some(9));
477+
478+
// Early stop updates state correctly at the end.
479+
let mut it = 0..=100;
480+
it.try_rfold((), |_, x| (x != 0).then_some(()));
481+
assert!(it.is_empty());
482+
assert_eq!(it.next(), None);
425483
}
426484

427485
#[test]

0 commit comments

Comments
 (0)