From 3c9e059eca85219d8ac2712a918b728db3db7a80 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 19:58:47 +0200 Subject: [PATCH 01/13] Inline `size_hint::mul_scalar` into `InterleaveShortest::size_hint` Simplifies subsequent diffs. The function was not used anywhere else, so I cut-pasted it. --- src/adaptors/mod.rs | 8 +++++++- src/size_hint.rs | 9 --------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index b5c0d42b1..f6ffa70c3 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -176,8 +176,14 @@ where }; let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; + fn size_hint_mul_scalar(sh: SizeHint, x: usize) -> SizeHint { + let (mut low, mut hi) = sh; + low = low.saturating_mul(x); + hi = hi.and_then(|elt| elt.checked_mul(x)); + (low, hi) + } let (combined_lower, combined_upper) = - size_hint::mul_scalar(size_hint::min(curr_hint, next_hint), 2); + size_hint_mul_scalar(size_hint::min(curr_hint, next_hint), 2); let lower = if curr_lower > next_lower { combined_lower + 1 } else { diff --git a/src/size_hint.rs b/src/size_hint.rs index f46f8fb14..9951f1b94 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -47,15 +47,6 @@ pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint { (low, hi) } -/// Multiply `x` correctly with a `SizeHint`. -#[inline] -pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint { - let (mut low, mut hi) = sh; - low = low.saturating_mul(x); - hi = hi.and_then(|elt| elt.checked_mul(x)); - (low, hi) -} - /// Return the maximum #[inline] pub fn max(a: SizeHint, b: SizeHint) -> SizeHint { From feddef1e9c2abc4c481e693a203d3a571748affa Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 19:58:47 +0200 Subject: [PATCH 02/13] Bring `size_hint::min` into `InterleaveShortest::size_hint` as size_hint_min Simplifies subsequent diffs. The function is in other places, so I copy-pasted it. --- src/adaptors/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index f6ffa70c3..4f8c9a11b 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -176,6 +176,16 @@ where }; let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; + pub fn size_hint_min(a: SizeHint, b: SizeHint) -> SizeHint { + let (a_lower, a_upper) = a; + let (b_lower, b_upper) = b; + let lower = std::cmp::min(a_lower, b_lower); + let upper = match (a_upper, b_upper) { + (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), + _ => a_upper.or(b_upper), + }; + (lower, upper) + } fn size_hint_mul_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; low = low.saturating_mul(x); @@ -183,7 +193,7 @@ where (low, hi) } let (combined_lower, combined_upper) = - size_hint_mul_scalar(size_hint::min(curr_hint, next_hint), 2); + size_hint_mul_scalar(size_hint_min(curr_hint, next_hint), 2); let lower = if curr_lower > next_lower { combined_lower + 1 } else { From 7e5cd8d1d653d1e349fdc2724c8da83b9e8bc136 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:04:59 +0200 Subject: [PATCH 03/13] size_hint_min is not a fn anymore --- src/adaptors/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 4f8c9a11b..7c6c7260c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -176,24 +176,21 @@ where }; let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; - pub fn size_hint_min(a: SizeHint, b: SizeHint) -> SizeHint { - let (a_lower, a_upper) = a; - let (b_lower, b_upper) = b; - let lower = std::cmp::min(a_lower, b_lower); - let upper = match (a_upper, b_upper) { + let size_hint_min = { + let lower = std::cmp::min(curr_lower, next_lower); + let upper = match (curr_upper, next_upper) { (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), - _ => a_upper.or(b_upper), + _ => curr_upper.or(next_upper), }; (lower, upper) - } + }; fn size_hint_mul_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; low = low.saturating_mul(x); hi = hi.and_then(|elt| elt.checked_mul(x)); (low, hi) } - let (combined_lower, combined_upper) = - size_hint_mul_scalar(size_hint_min(curr_hint, next_hint), 2); + let (combined_lower, combined_upper) = size_hint_mul_scalar(size_hint_min, 2); let lower = if curr_lower > next_lower { combined_lower + 1 } else { From ed4b5622ed0988f31e57c80feb980d739fafc9b9 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:11:29 +0200 Subject: [PATCH 04/13] size_hint_mul_scalar is not a fn anymore --- src/adaptors/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 7c6c7260c..bb66ddf36 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -184,13 +184,12 @@ where }; (lower, upper) }; - fn size_hint_mul_scalar(sh: SizeHint, x: usize) -> SizeHint { - let (mut low, mut hi) = sh; - low = low.saturating_mul(x); - hi = hi.and_then(|elt| elt.checked_mul(x)); + let (combined_lower, combined_upper) = { + let (mut low, mut hi) = size_hint_min; + low = low.saturating_mul(2); + hi = hi.and_then(|elt| elt.checked_mul(2)); (low, hi) - } - let (combined_lower, combined_upper) = size_hint_mul_scalar(size_hint_min, 2); + }; let lower = if curr_lower > next_lower { combined_lower + 1 } else { From 76d9730a765ae72f97c401b2f174e51c233c1a66 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:15:36 +0200 Subject: [PATCH 05/13] Compute `size_hint_min_[lower|upper]` and `combined_[lower|upper]` separately --- src/adaptors/mod.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index bb66ddf36..1385be953 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -176,20 +176,13 @@ where }; let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; - let size_hint_min = { - let lower = std::cmp::min(curr_lower, next_lower); - let upper = match (curr_upper, next_upper) { - (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), - _ => curr_upper.or(next_upper), - }; - (lower, upper) - }; - let (combined_lower, combined_upper) = { - let (mut low, mut hi) = size_hint_min; - low = low.saturating_mul(2); - hi = hi.and_then(|elt| elt.checked_mul(2)); - (low, hi) + let size_hint_min_lower = std::cmp::min(curr_lower, next_lower); + let size_hint_min_upper = match (curr_upper, next_upper) { + (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), + _ => curr_upper.or(next_upper), }; + let combined_lower = size_hint_min_lower.saturating_mul(2); + let combined_upper = size_hint_min_upper.and_then(|elt| elt.checked_mul(2)); let lower = if curr_lower > next_lower { combined_lower + 1 } else { From 4753e13873271537a8a3b7ef42f0efcd8f8224e7 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:17:15 +0200 Subject: [PATCH 06/13] Move `[size_hint_min|combined]_upper` closer to where they are used --- src/adaptors/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 1385be953..8ca6041ab 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -177,12 +177,7 @@ where let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; let size_hint_min_lower = std::cmp::min(curr_lower, next_lower); - let size_hint_min_upper = match (curr_upper, next_upper) { - (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), - _ => curr_upper.or(next_upper), - }; let combined_lower = size_hint_min_lower.saturating_mul(2); - let combined_upper = size_hint_min_upper.and_then(|elt| elt.checked_mul(2)); let lower = if curr_lower > next_lower { combined_lower + 1 } else { @@ -194,6 +189,11 @@ where (None, Some(_)) => true, (Some(curr_max), Some(next_max)) => curr_max > next_max, }; + let size_hint_min_upper = match (curr_upper, next_upper) { + (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), + _ => curr_upper.or(next_upper), + }; + let combined_upper = size_hint_min_upper.and_then(|elt| elt.checked_mul(2)); if extra_elem { combined_upper.and_then(|x| x.checked_add(1)) } else { From 0b13191ee650bf122140ab9f44e2f12deed6852c Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:20:00 +0200 Subject: [PATCH 07/13] Eliminate `[size_hint_min|combined]_lower` Before this commit, we repeated the comparison between `curr_lower` and `next_lower`, because we did it explicitly, and implicitly through `cmp::min`. Now, because we rely on one comparison. This simplifies the logic: If `curr_lower > next_lower`, then the "next" iterator is the limitting factor, and there's one additional element at the end coming from the "current" iterator. Otherwise, the "current" iterator is the limitting factor, and for each element of the "current" iterator, there's one from the "next" iterator, so we do not have to add 1. --- src/adaptors/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 8ca6041ab..924e7a841 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -176,12 +176,10 @@ where }; let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; - let size_hint_min_lower = std::cmp::min(curr_lower, next_lower); - let combined_lower = size_hint_min_lower.saturating_mul(2); let lower = if curr_lower > next_lower { - combined_lower + 1 + next_lower.saturating_mul(2) + 1 } else { - combined_lower + curr_lower.saturating_mul(2) }; let upper = { let extra_elem = match (curr_upper, next_upper) { From 93fee96597bafb6ab74b1bca7e9215d4cb10e095 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:27:58 +0200 Subject: [PATCH 08/13] Do one `match` instead of two --- src/adaptors/mod.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 924e7a841..fa859282d 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -182,14 +182,13 @@ where curr_lower.saturating_mul(2) }; let upper = { - let extra_elem = match (curr_upper, next_upper) { - (_, None) => false, - (None, Some(_)) => true, - (Some(curr_max), Some(next_max)) => curr_max > next_max, - }; - let size_hint_min_upper = match (curr_upper, next_upper) { - (Some(u1), Some(u2)) => Some(std::cmp::min(u1, u2)), - _ => curr_upper.or(next_upper), + let (extra_elem, size_hint_min_upper) = match (curr_upper, next_upper) { + (Some(curr_max), Some(next_max)) => { + (curr_max > next_max, Some(std::cmp::min(curr_max, next_max))) + } + (Some(curr_max), None) => (false, Some(curr_max)), + (None, Some(next_max)) => (true, Some(next_max)), + (None, None) => (false, None), }; let combined_upper = size_hint_min_upper.and_then(|elt| elt.checked_mul(2)); if extra_elem { From 7646f87490ce3e2b9b2d49b33c70d3ab2ae52f66 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:29:36 +0200 Subject: [PATCH 09/13] Rely on one comparison of `curr_max` and `next_max` instead of two --- src/adaptors/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index fa859282d..dcda237f9 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -184,7 +184,11 @@ where let upper = { let (extra_elem, size_hint_min_upper) = match (curr_upper, next_upper) { (Some(curr_max), Some(next_max)) => { - (curr_max > next_max, Some(std::cmp::min(curr_max, next_max))) + if curr_max > next_max { + (true, Some(next_max)) + } else { + (false, Some(curr_max)) + } } (Some(curr_max), None) => (false, Some(curr_max)), (None, Some(next_max)) => (true, Some(next_max)), From b5168061454ca391246ff7faaa17780057cad74c Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:35:01 +0200 Subject: [PATCH 10/13] Eliminate `size_hint_min_upper` This avoids constructing an `Option`, just to later inspect it and map it via `and_then`. It's a bit more code, but I'd argue it's clearer. --- src/adaptors/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index dcda237f9..3158f4a67 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -182,19 +182,18 @@ where curr_lower.saturating_mul(2) }; let upper = { - let (extra_elem, size_hint_min_upper) = match (curr_upper, next_upper) { + let (extra_elem, combined_upper) = match (curr_upper, next_upper) { (Some(curr_max), Some(next_max)) => { if curr_max > next_max { - (true, Some(next_max)) + (true, next_max.checked_mul(2)) } else { - (false, Some(curr_max)) + (false, curr_max.checked_mul(2)) } } - (Some(curr_max), None) => (false, Some(curr_max)), - (None, Some(next_max)) => (true, Some(next_max)), + (Some(curr_max), None) => (false, curr_max.checked_mul(2)), + (None, Some(next_max)) => (true, next_max.checked_mul(2)), (None, None) => (false, None), }; - let combined_upper = size_hint_min_upper.and_then(|elt| elt.checked_mul(2)); if extra_elem { combined_upper.and_then(|x| x.checked_add(1)) } else { From 027905e846df81af1752fe370c9836dc29107a74 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 30 Jun 2026 20:38:33 +0200 Subject: [PATCH 11/13] Eliminate `extra_elem` We now do the `checked_add(1)` inline instead of computing `extra_elem` and then looking back to see if we shall add 1. Now, the computation of `lower` and `upper` is (at least somewhat) in line: Check if curr or next is the limitting factor and behave accordingly. --- src/adaptors/mod.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 3158f4a67..3fd45db1c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -181,24 +181,17 @@ where } else { curr_lower.saturating_mul(2) }; - let upper = { - let (extra_elem, combined_upper) = match (curr_upper, next_upper) { - (Some(curr_max), Some(next_max)) => { - if curr_max > next_max { - (true, next_max.checked_mul(2)) - } else { - (false, curr_max.checked_mul(2)) - } + let upper = match (curr_upper, next_upper) { + (Some(curr_max), Some(next_max)) => { + if curr_max > next_max { + next_max.checked_mul(2).and_then(|x| x.checked_add(1)) + } else { + curr_max.checked_mul(2) } - (Some(curr_max), None) => (false, curr_max.checked_mul(2)), - (None, Some(next_max)) => (true, next_max.checked_mul(2)), - (None, None) => (false, None), - }; - if extra_elem { - combined_upper.and_then(|x| x.checked_add(1)) - } else { - combined_upper } + (Some(curr_max), None) => curr_max.checked_mul(2), + (None, Some(next_max)) => next_max.checked_mul(2).and_then(|x| x.checked_add(1)), + (None, None) => None, }; (lower, upper) } From ec940ac790645e966540bbffa26b986f70253c26 Mon Sep 17 00:00:00 2001 From: Patrick Wehbe Date: Fri, 19 Jun 2026 22:55:44 +0300 Subject: [PATCH 12/13] Added test for `InterleaveShortest::size_hint` with `usize::MAX` --- tests/test_std.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 5f5cfd1eb..838ca9011 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -86,6 +86,16 @@ fn interleave_shortest() { assert_eq!(it.size_hint(), (6, Some(6))); } +#[test] +fn interleave_shortest_size_hint_does_not_overflow() { + // The combined lower bound can exceed `usize::MAX`, which used to overflow + // when computing `size_hint`. It should saturate instead of panicking. + let i = ::std::iter::repeat(0u8).take(usize::MAX); + let j = ::std::iter::repeat(0u8).take(usize::MAX - 1); + let it = i.interleave_shortest(j); + assert_eq!(it.size_hint(), (usize::MAX, None)); +} + #[test] fn duplicates_by() { let xs = ["aaa", "bbbbb", "aa", "ccc", "bbbb", "aaaaa", "cccc"]; From 881101f9d45075be663f8bb99c80b3bb9a957cb0 Mon Sep 17 00:00:00 2001 From: Patrick Wehbe Date: Fri, 19 Jun 2026 22:55:44 +0300 Subject: [PATCH 13/13] Use `saturating_add` in `InterleaveShortest::size_hint` If "next" is the limitting factor, there may be one element from "curr" at the end. But if this element leads to more than `usize::MAX` elements, a lower bound of `usize::MAX` is still good enough. Fixes #1068 --- src/adaptors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 3fd45db1c..23a86990c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -177,7 +177,7 @@ where let (curr_lower, curr_upper) = curr_hint; let (next_lower, next_upper) = next_hint; let lower = if curr_lower > next_lower { - next_lower.saturating_mul(2) + 1 + next_lower.saturating_mul(2).saturating_add(1) } else { curr_lower.saturating_mul(2) };