This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 136de0d663 fix: don't panic in `GenericByteArray::from_iter_values` 
(#10729)
136de0d663 is described below

commit 136de0d6636aa40c1af846257b561fd88dd52ac4
Author: Emil Ernerfeldt <[email protected]>
AuthorDate: Tue Aug 18 17:38:15 2026 -0700

    fix: don't panic in `GenericByteArray::from_iter_values` (#10729)
    
    # Which issue does this PR close?
    
    * Follow-up to https://github.com/apache/arrow-rs/pull/10656
    (https://github.com/apache/arrow-rs/pull/10656#discussion_r3771712125)
    * Part of https://github.com/apache/arrow-rs/issues/10553
    
    # Rationale for this change
    
    `size_hint` is only a hint: an iterator may report no upper bound at
    all, and is [not
    
required](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.size_hint)
    to yield the number of items it reports. `from_iter_values` used the
    upper bound only to pre-allocate, yet panicked when there was none.
    
    # What changes are included in this PR?
    
    `GenericByteArray::from_iter_values` now falls back to the lower bound
    when there is no upper bound, and no longer panics. The `# Panics` doc
    is updated.
    
    This was the only such case: the remaining `size_hint().1.expect(…)`
    sites are all in `unsafe` trusted-length APIs, where the panic guards a
    safety contract.
    
    # Are these changes tested?
    
    Yes, new test with an iterator that reports no upper bound, and one that
    over-reports.
    
    # Are there any user-facing changes?
    
    One less panic. No breaking changes.
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 arrow-array/src/array/byte_array.rs | 37 +++++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/arrow-array/src/array/byte_array.rs 
b/arrow-array/src/array/byte_array.rs
index 74b9d0353b..be4ecdee50 100644
--- a/arrow-array/src/array/byte_array.rs
+++ b/arrow-array/src/array/byte_array.rs
@@ -216,18 +216,23 @@ impl<T: ByteArrayType> GenericByteArray<T> {
     /// Creates a [`GenericByteArray`] based on an iterator of values without 
nulls
     ///
     /// # Panics
-    /// Panics if the iterator has no upper bound on its size hint, or if the 
total
-    /// length of the values exceeds `T::Offset::MAX`
+    /// Panics if the total length of the values exceeds `T::Offset::MAX`
     pub fn from_iter_values<Ptr, I>(iter: I) -> Self
     where
         Ptr: AsRef<T::Native>,
         I: IntoIterator<Item = Ptr>,
     {
         let iter = iter.into_iter();
-        let (_, data_len) = iter.size_hint();
-        let data_len = data_len.expect("Iterator must be sized"); // panic if 
no upper bound.
-
-        let mut offsets = MutableBuffer::new((data_len + 1) * 
std::mem::size_of::<T::Offset>());
+        // The size hint is only used to pre-allocate: an iterator is free to 
yield
+        // a different number of items than it reports.
+        let (lower, upper) = iter.size_hint();
+        let capacity = upper.unwrap_or(lower);
+
+        let mut offsets = MutableBuffer::new(
+            capacity
+                .saturating_add(1)
+                .saturating_mul(std::mem::size_of::<T::Offset>()),
+        );
         offsets.push(T::Offset::usize_as(0));
 
         let mut values = MutableBuffer::new(0);
@@ -640,6 +645,26 @@ mod tests {
     use crate::{Array, BinaryArray, StringArray};
     use arrow_buffer::{Buffer, NullBuffer, OffsetBuffer};
 
+    /// `from_iter_values` must work with iterators that report no upper size 
bound,
+    /// and must not trust the size hint it does get.
+    #[test]
+    fn from_iter_values_untrusted_size_hint() {
+        // No upper bound at all:
+        let no_upper_bound = (0..20).filter(|i| i % 2 == 0).map(|i| 
format!("v{i}"));
+        assert_eq!(no_upper_bound.size_hint(), (0, Some(20)));
+        let array = StringArray::from_iter_values(no_upper_bound);
+        assert_eq!(array.len(), 10);
+        assert_eq!(array.value(0), "v0");
+        assert_eq!(array.value(9), "v18");
+
+        // Upper bound larger than the number of yielded values:
+        let too_large_upper_bound = (0..).map(|i| 
format!("v{i}")).take_while(|v| v != "v3");
+        assert_eq!(too_large_upper_bound.size_hint(), (0, None));
+        let array = StringArray::from_iter_values(too_large_upper_bound);
+        assert_eq!(array.len(), 3);
+        assert_eq!(array.value(2), "v2");
+    }
+
     #[test]
     fn try_new() {
         let data = Buffer::from_slice_ref("helloworld");

Reply via email to