Repository: fitzgen/bumpalo
Pull request #262 (2025-01-26 14:42:27)

Add fallible slice allocation functions
This change adds slice allocation functions  that can fail, to match the scalar allocation functions that can fail.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6170a7..b882a6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,32 @@ Released YYYY-MM-DD.
 
 --------------------------------------------------------------------------------
 
+## 3.17.0
+
+Released 2025-01-28.
+
+### Added
+
+* Added a bunch of `try_` allocation methods for slices and `str`:
+  * `try_alloc_slice_fill_default`
+  * `try_alloc_slice_fill_iter`
+  * `try_alloc_slice_fill_clone`
+  * `try_alloc_slice_fill_copy`
+  * `try_alloc_slice_fill_with`
+  * `try_alloc_str`
+  * `try_alloc_slice_clone`
+  * `try_alloc_slice_copy`
+
+### Changed
+
+* Minimum supported Rust version reduced to 1.71.1
+
+### Fixed
+
+* Fixed a stacked-borrows MIRI bug in `dealloc`
+
+--------------------------------------------------------------------------------
+
 ## 3.16.0
 
 Released 2024-04-08.
diff --git a/Cargo.toml b/Cargo.toml
index 6774650..e16758c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,13 +4,13 @@ categories = ["memory-management", "rust-patterns", "no-std"]
 description = "A fast bump allocation arena for Rust."
 documentation = "https://docs.rs/bumpalo"
 edition = "2021"
+exclude = ["/.github/*", "/benches", "/tests", "valgrind.supp", "bumpalo.png"]
 license = "MIT OR Apache-2.0"
 name = "bumpalo"
 readme = "README.md"
 repository = "https://github.com/fitzgen/bumpalo"
-version = "3.16.0"
-exclude = ["/.github/*", "/benches", "/tests", "valgrind.supp", "bumpalo.png"]
 rust-version = "1.71.1"
+version = "3.17.0"
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/src/lib.rs b/src/lib.rs
index 3e3201a..28e8351 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1351,6 +1351,58 @@ impl Bump {
         }
     }
 
+    /// Allocates a new slice of size `len` into this `Bump` and returns an
+    /// exclusive reference to the copy, failing if the closure return an Err.
+    ///
+    /// The elements of the slice are initialized using the supplied closure.
+    /// The closure argument is the position in the slice.
+    ///
+    /// ## Panics
+    ///
+    /// Panics if reserving space for the slice fails.
+    ///
+    /// ## Example
+    ///
+    /// ```
+    /// let bump = bumpalo::Bump::new();
+    /// let x: Result<&mut [usize], ()> = bump.alloc_slice_try_fill_with(5, |i| Ok(5 * i));
+    /// assert_eq!(x, Ok(bump.alloc_slice_copy(&[0, 5, 10, 15, 20])));
+    /// ```
+    ///
+    /// ```
+    /// let bump = bumpalo::Bump::new();
+    /// let x: Result<&mut [usize], ()> = bump.alloc_slice_try_fill_with(
+    ///    5,
+    ///    |n| if n == 2 { Err(()) } else { Ok(n) }
+    /// );
+    /// assert_eq!(x, Err(()));
+    /// ```
+    #[inline(always)]
+    pub fn alloc_slice_try_fill_with<T, F, E>(&self, len: usize, mut f: F) -> Result<&mut [T], E>
+    where
+        F: FnMut(usize) -> Result<T, E>,
+    {
+        let layout = Layout::array::<T>(len).unwrap_or_else(|_| oom());
+        let base_ptr = self.alloc_layout(layout);
+        let dst = base_ptr.cast::<T>();
+
+        unsafe {
+            for i in 0..len {
+                match f(i) {
+                    Ok(el) => ptr::write(dst.as_ptr().add(i), el),
+                    Err(e) => {
+                        self.dealloc(base_ptr, layout);
+                        return Err(e);
+                    }
+                }
+            }
+
+            let result = slice::from_raw_parts_mut(dst.as_ptr(), len);
+            debug_assert_eq!(Layout::for_value(result), layout);
+            Ok(result)
+        }
+    }
+
     /// Allocates a new slice of size `len` into this `Bump` and returns an
     /// exclusive reference to the copy.
     ///
@@ -1487,6 +1539,45 @@ impl Bump {
         })
     }
 
+    /// Allocates a new slice of size `len` slice into this `Bump` and return an
+    /// exclusive reference to the copy, failing if the iterator returns an Err.
+    ///
+    /// The elements are initialized using the supplied iterator.
+    ///
+    /// ## Panics
+    ///
+    /// Panics if reserving space for the slice fails, or if the supplied
+    /// iterator returns fewer elements than it promised.
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// let bump = bumpalo::Bump::new();
+    /// let x: Result<&mut [i32], ()> = bump.alloc_slice_try_fill_iter(
+    ///    [2, 3, 5].iter().cloned().map(|i| Ok(i * i))
+    /// );
+    /// assert_eq!(x, Ok(bump.alloc_slice_copy(&[4, 9, 25])));
+    /// ```
+    ///
+    /// ```
+    /// let bump = bumpalo::Bump::new();
+    /// let x: Result<&mut [i32], ()> = bump.alloc_slice_try_fill_iter(
+    ///    [Ok(2), Err(()), Ok(5)].iter().cloned()
+    /// );
+    /// assert_eq!(x, Err(()));
+    /// ```
+    #[inline(always)]
+    pub fn alloc_slice_try_fill_iter<T, I, E>(&self, iter: I) -> Result<&mut [T], E>
+    where
+        I: IntoIterator<Item = Result<T, E>>,
+        I::IntoIter: ExactSizeIterator,
+    {
+        let mut iter = iter.into_iter();
+        self.alloc_slice_try_fill_with(iter.len(), |_| {
+            iter.next().expect("Iterator supplied too few elements")
+        })
+    }
+
     /// Allocates a new slice of size `iter.len()` slice into this `Bump` and return an
     /// exclusive reference to the copy. Does not panic on failure.
     ///


Test patch:
diff --git a/tests/all/alloc_fill.rs b/tests/all/alloc_fill.rs
index 2381136..399e7b6 100644
--- a/tests/all/alloc_fill.rs
+++ b/tests/all/alloc_fill.rs
@@ -1,6 +1,7 @@
 use bumpalo::Bump;
 use std::alloc::Layout;
 use std::cmp;
+use std::iter::repeat;
 use std::mem;
 
 #[test]
@@ -29,6 +30,35 @@ fn alloc_slice_fill_zero() {
     assert_eq!(ptr2 as *mut _ as usize, ptr3.as_ptr() as usize + 1);
 }
 
+#[test]
+fn alloc_slice_try_fill_with_succeeds() {
+    let b = Bump::new();
+    let res: Result<&mut [usize], ()> = b.alloc_slice_try_fill_with(100, |n| Ok(n));
+    assert_eq!(res.map(|arr| arr[50]), Ok(50));
+}
+
+#[test]
+fn alloc_slice_try_fill_with_fails() {
+    let b = Bump::new();
+    let res: Result<&mut [u16], ()> =
+        b.alloc_slice_try_fill_with(1000, |n| if n == 100 { Err(()) } else { Ok(42) });
+    assert_eq!(res, Err(()));
+}
+
+#[test]
+fn alloc_slice_try_fill_iter_succeeds() {
+    let b = Bump::new();
+    let res: Result<&mut [u16], ()> = b.alloc_slice_try_fill_iter(repeat(42).take(10).map(Ok));
+    assert_eq!(res.map(|arr| arr[5]), Ok(42));
+}
+
+#[test]
+fn alloc_slice_try_fill_iter_fails() {
+    let b = Bump::new();
+    let res: Result<&mut [u16], ()> = b.alloc_slice_try_fill_iter(repeat(()).take(10).map(Err));
+    assert_eq!(res, Err(()));
+}
+
 #[test]
 #[should_panic(expected = "out of memory")]
 fn alloc_slice_overflow() {
diff --git a/tests/all/alloc_try_with.rs b/tests/all/alloc_try_with.rs
index 4521386..a9a4273 100644
--- a/tests/all/alloc_try_with.rs
+++ b/tests/all/alloc_try_with.rs
@@ -4,6 +4,8 @@
 // We only run them when debug_assertions are not set, as we expect them to fail outside release
 // mode.
 
+use std::iter::repeat;
+
 use bumpalo::Bump;
 
 #[test]
@@ -116,3 +118,23 @@ fn alloc_try_with_large_enum_err() {
         .alloc_try_with(|| Result::<LargeEnum, _>::Err(()))
         .is_err());
 }
+
+#[test]
+#[cfg_attr(debug_assertions, ignore)]
+fn alloc_slice_try_fill_with_large_length() {
+    let b = Bump::new();
+
+    assert!(b
+        .alloc_slice_try_fill_with(10_000_000, |_| Err::<u8, _>(()))
+        .is_err());
+}
+
+#[test]
+#[cfg_attr(debug_assertions, ignore)]
+fn alloc_slice_try_fill_iter_large_length() {
+    let b = Bump::new();
+
+    assert!(b
+        .alloc_slice_try_fill_iter(repeat(Err::<u8, _>(())).take(10_000_000))
+        .is_err());
+}