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 6e25fe3a67 fix(arrow-buffer): add a fallible collect_bool (#10984)
6e25fe3a67 is described below

commit 6e25fe3a67584f7286da327176dec8da0d40b5e9
Author: pawan <[email protected]>
AuthorDate: Mon Sep 7 06:18:32 2026 +0530

    fix(arrow-buffer): add a fallible collect_bool (#10984)
    
    # Which issue does this PR close?
    
    - Closes #10973.
    
    # Rationale for this change
    
    `collect_bool` reserves ceil(len / 64) u64 words before it calls the
    closure once, so a `len` that comes from a row or bit count in user
    controlled data aborts the process rather than failing. the reservation
    went through `Vec::with_capacity`, which has no way to report that.
    
    this file already has the shape for it. `with_capacity` is
    `try_with_capacity(..).unwrap_or_else(|e| panic!("{e}"))` at line 154,
    and there are seven other fallible twins beside it:
    `try_from_len_zeroed`, `try_reserve`, `try_repeat_slice_n_times`,
    `try_resize`, `try_shrink_to_fit`, `try_extend_from_slice` and
    `try_extend_zeros`. `collect_bool` is the allocating entry point that
    does not have one.
    
    # What changes are included in this PR?
    
    - add `try_collect_bool` returning `Result<Self, MutableBufferError>`
    - `collect_bool` calls it and unwraps, the same way `with_capacity`
    calls `try_with_capacity`
    - the reservation goes through `Vec::try_reserve`, and a failure maps
    onto the existing `AllocationError` variant carrying the layout it tried
    to take
    
    no behaviour change for any `len` that already worked. `collect_bool`
    still panics, one level down, and its doc comment now says so.
    
    i went with a fallible entry point rather than capping the reservation,
    since a cap only moves where the abort happens rather than letting a
    caller handle it. happy to do the cap instead if you would rather not
    grow the api surface.
    
    # Are these changes tested?
    
    yes, two tests.
    
    `try_collect_bool_reports_a_len_it_cannot_reserve` uses the 2^60 from
    the issue and asserts it comes back as `AllocationError` and that the
    closure ran zero times. the reproducer in the issue aborts on that same
    input with `memory allocation of 144115188075855872 bytes failed`.
    
    `try_collect_bool_matches_collect_bool_for_sizes_that_fit` checks the
    two agree byte for byte at 0, 1, 63, 64, 65 and 1000 bits, since 64 is
    the word boundary and 8 is the truncation boundary.
    
    `arrow-buffer` is 351 passed, `arrow-array` is 721 passed, and fmt and
    clippy with `-D warnings` are clean. i ran arrow-array as well because
    `collect_bool` has around twenty call sites in it and this changes how
    the function is built.
    
    # Are there any user-facing changes?
    
    `try_collect_bool` is new and additive. `collect_bool` keeps its
    signature and still panics on a `len` it cannot reserve.
    
    ---------
    
    Co-authored-by: Jeffrey Vo <[email protected]>
---
 arrow-buffer/src/buffer/mutable.rs | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/arrow-buffer/src/buffer/mutable.rs 
b/arrow-buffer/src/buffer/mutable.rs
index 7ae7d1337f..610d9a738b 100644
--- a/arrow-buffer/src/buffer/mutable.rs
+++ b/arrow-buffer/src/buffer/mutable.rs
@@ -737,9 +737,31 @@ impl MutableBuffer {
     ///
     /// This is similar to `from_trusted_len_iter_bool`, however, can be 
significantly faster
     /// as it eliminates the conditional `Iterator::next`
+    ///
+    /// # Panics
+    ///
+    /// Panics if the backing storage for `len` bits cannot be allocated. Use
+    /// [`MutableBuffer::try_collect_bool`] for a fallible version.
+    #[inline]
+    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, f: F) -> Self {
+        Self::try_collect_bool(len, f).unwrap_or_else(|e| panic!("{e}"))
+    }
+
+    /// Fallible version of [`MutableBuffer::collect_bool`].
+    ///
+    /// `len` is a bit count, so the reservation is `ceil(len / 64)` u64 
slots. This function
+    /// returns an error if that much memory cannot be reserved up front.
     #[inline]
-    pub fn collect_bool<F: FnMut(usize) -> bool>(len: usize, mut f: F) -> Self 
{
-        let mut buffer: Vec<u64> = Vec::with_capacity(bit_util::ceil(len, 64));
+    pub fn try_collect_bool<F: FnMut(usize) -> bool>(
+        len: usize,
+        mut f: F,
+    ) -> Result<Self, MutableBufferError> {
+        let words = bit_util::ceil(len, 64);
+        let layout = Layout::array::<u64>(words).map_err(|_| 
MutableBufferError::LayoutError)?;
+        let mut buffer: Vec<u64> = Vec::new();
+        buffer
+            .try_reserve(words)
+            .map_err(|_| MutableBufferError::AllocationError(layout))?;
 
         let chunks = len / 64;
         let remainder = len % 64;
@@ -765,7 +787,7 @@ impl MutableBuffer {
 
         let mut buffer: MutableBuffer = buffer.into();
         buffer.truncate(bit_util::ceil(len, 8));
-        buffer
+        Ok(buffer)
     }
 
     /// Extends this buffer with boolean values.

Reply via email to