Repository: fitzgen/bumpalo
Pull request #266 (2025-02-24 16:38:56)

Add serde serialization for String
Previously (#210 and d746a56a51b73a53eb666f1a29961466ab84478a), `serde` support was added to `Vec` and `Box` but not `String`. This fills that gap. 

Additionally reorganize tests to split the ones that depend on `collections` and `boxed` features, as before, running `cargo test --features=boxed,serde` didn't run the tests for `Box` as they were under a `collections` feature guard.

Related to #63


Patch:
diff --git a/README.md b/README.md
index b2aaa20..5cdac51 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,6 @@ and then start bump allocating into this new memory chunk.
 
 ```rust
 use bumpalo::Bump;
-use std::u64;
 
 struct Doggo {
     cuteness: u64,
@@ -63,7 +62,7 @@ let bump = Bump::new();
 
 // Allocate values into the arena.
 let scooter = bump.alloc(Doggo {
-    cuteness: u64::max_value(),
+    cuteness: u64::MAX,
     age: 8,
     scritches_required: true,
 });
@@ -155,8 +154,8 @@ in its space itself.
 
 #### Serde
 
-Adding the `serde` feature flag will enable transparent serialization of Vecs and 
-boxed values.
+Adding the `serde` feature flag will enable transparent serialization of `Vec`s, `String`s
+and boxed values.
 
 ```toml
 [dependencies]
diff --git a/src/collections/string.rs b/src/collections/string.rs
index e9fafbf..85f1123 100644
--- a/src/collections/string.rs
+++ b/src/collections/string.rs
@@ -2150,3 +2150,19 @@ impl<'a, 'bump> DoubleEndedIterator for Drain<'a, 'bump> {
 }
 
 impl<'a, 'bump> FusedIterator for Drain<'a, 'bump> {}
+
+#[cfg(feature = "serde")]
+mod serialize {
+    use super::*;
+
+    use serde::{Serialize, Serializer};
+
+    impl<'bump> Serialize for String<'bump> {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            serializer.serialize_str(&self)
+        }
+    }
+}
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());
+}
diff --git a/tests/all/serde.rs b/tests/all/serde.rs
deleted file mode 100644
index 6270095..0000000
--- a/tests/all/serde.rs
+++ /dev/null
@@ -1,119 +0,0 @@
-#![cfg(feature = "collections")]
-#![cfg(feature = "serde")]
-
-use bumpalo::{boxed::Box, vec, Bump};
-use serde::{Deserialize, Serialize};
-
-macro_rules! compare_std_vec {
-    (in $bump:ident; $($x:expr),+) => {{
-        let vec = vec![in &$bump; $($x),+];
-        let std_vec = std::vec![$($x),+];
-        (vec, std_vec)
-    }}
-}
-
-macro_rules! compare_std_box {
-    (in $bump:ident; $x:expr) => {
-        (Box::new_in($x, &$bump), std::boxed::Box::new($x))
-    };
-}
-
-macro_rules! assert_eq_json {
-    ($a:ident, $b:ident) => {
-        assert_eq!(
-            serde_json::to_string(&$a).unwrap(),
-            serde_json::to_string(&$b).unwrap(),
-        )
-    };
-}
-
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
-#[serde(tag = "t", content = "c")]
-enum Test {
-    First,
-    Second,
-}
-
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
-#[serde()]
-struct Mixed {
-    i: i32,
-    s: String,
-    o: Option<String>,
-    e: Test,
-}
-
-#[test]
-fn test_vec_serializes_str() {
-    let bump = Bump::new();
-    let (vec, std_vec) = compare_std_vec![in bump; "hello", "world"];
-    assert_eq_json!(vec, std_vec);
-    let de: std::vec::Vec<String> =
-        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
-    assert_eq!(de, std_vec);
-}
-
-#[test]
-fn test_vec_serializes_f32() {
-    let bump = Bump::new();
-    let (vec, std_vec) = compare_std_vec![in bump; 1.5707964, 3.1415927];
-    assert_eq_json!(vec, std_vec);
-    let de: std::vec::Vec<f32> =
-        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
-    assert_eq!(de, std_vec);
-}
-
-#[cfg(feature = "serde")]
-#[test]
-fn test_vec_serializes_complex() {
-    let bump = Bump::new();
-    let (vec, std_vec) = compare_std_vec![
-        in bump;
-        Mixed {
-            i: 8,
-            s: "a".into(),
-            o: None,
-            e: Test::Second,
-        },
-        Mixed {
-            i: 8,
-            s: "b".into(),
-            o: Some("some".into()),
-            e: Test::First,
-        }
-    ];
-    assert_eq_json!(vec, std_vec);
-    let de: std::vec::Vec<Mixed> =
-        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
-    assert_eq!(de, std_vec);
-}
-
-#[test]
-fn test_box_serializes() {
-    let bump = Bump::new();
-    let (box_int, std_box_int) = compare_std_box!(in bump; 1);
-    assert_eq_json!(box_int, std_box_int);
-    let (box_str, std_box_str) = compare_std_box!(in bump; 1);
-    assert_eq_json!(box_str, std_box_str);
-    let (box_vec, std_box_vec) = compare_std_box!(in bump; std::vec!["hello", "world"]);
-    assert_eq_json!(box_vec, std_box_vec);
-}
-
-#[cfg(feature = "serde")]
-#[test]
-fn test_box_serializes_complex() {
-    let bump = Bump::new();
-    let (vec, std_vec) = compare_std_box![
-        in bump;
-        Mixed {
-            i: 8,
-            s: "a".into(),
-            o: None,
-            e: Test::Second,
-        }
-    ];
-    assert_eq_json!(vec, std_vec);
-    let de: std::boxed::Box<Mixed> =
-        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
-    assert_eq!(de, std_vec);
-}
diff --git a/tests/all/serde/boxed.rs b/tests/all/serde/boxed.rs
new file mode 100644
index 0000000..d79e514
--- /dev/null
+++ b/tests/all/serde/boxed.rs
@@ -0,0 +1,40 @@
+#![cfg(all(feature = "boxed", feature = "serde"))]
+
+use super::{assert_eq_json, Mixed, Test};
+
+use bumpalo::{boxed::Box, Bump};
+
+macro_rules! compare_std_box {
+    (in $bump:ident; $x:expr) => {
+        (Box::new_in($x, &$bump), std::boxed::Box::new($x))
+    };
+}
+
+#[test]
+fn test_box_serializes() {
+    let bump = Bump::new();
+    let (box_int, std_box_int) = compare_std_box!(in bump; 1);
+    assert_eq_json!(box_int, std_box_int);
+    let (box_str, std_box_str) = compare_std_box!(in bump; 1);
+    assert_eq_json!(box_str, std_box_str);
+    let (box_vec, std_box_vec) = compare_std_box!(in bump; std::vec!["hello", "world"]);
+    assert_eq_json!(box_vec, std_box_vec);
+}
+
+#[test]
+fn test_box_serializes_complex() {
+    let bump = Bump::new();
+    let (vec, std_vec) = compare_std_box![
+        in bump;
+        Mixed {
+            i: 8,
+            s: "a".into(),
+            o: None,
+            e: Test::Second,
+        }
+    ];
+    assert_eq_json!(vec, std_vec);
+    let de: std::boxed::Box<Mixed> =
+        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
+    assert_eq!(de, std_vec);
+}
diff --git a/tests/all/serde/mod.rs b/tests/all/serde/mod.rs
new file mode 100644
index 0000000..70ff91e
--- /dev/null
+++ b/tests/all/serde/mod.rs
@@ -0,0 +1,32 @@
+use serde::{Deserialize, Serialize};
+
+mod boxed;
+mod string;
+mod vec;
+
+#[macro_export]
+macro_rules! assert_eq_json {
+    ($a:ident, $b:ident) => {
+        assert_eq!(
+            serde_json::to_string(&$a).unwrap(),
+            serde_json::to_string(&$b).unwrap(),
+        )
+    };
+}
+use assert_eq_json;
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+#[serde(tag = "t", content = "c")]
+enum Test {
+    First,
+    Second,
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+#[serde()]
+struct Mixed {
+    i: i32,
+    s: std::string::String,
+    o: Option<std::string::String>,
+    e: Test,
+}
diff --git a/tests/all/serde/string.rs b/tests/all/serde/string.rs
new file mode 100644
index 0000000..02090d0
--- /dev/null
+++ b/tests/all/serde/string.rs
@@ -0,0 +1,24 @@
+#![cfg(all(feature = "collections", feature = "serde"))]
+
+use super::assert_eq_json;
+
+use bumpalo::{collections::string::String, Bump};
+
+macro_rules! compare_std_str {
+    (in $bump:ident; $x:expr) => {
+        (
+            String::from_str_in($x, &$bump),
+            std::string::String::from($x),
+        )
+    };
+}
+
+#[test]
+fn test_string_serializes() {
+    let bump = Bump::new();
+    let (str, std_str) = compare_std_str![in bump; "hello world !"];
+    assert_eq_json!(str, std_str);
+    let de: std::string::String =
+        serde_json::from_str(&serde_json::to_string(&str).unwrap()).unwrap();
+    assert_eq!(de, std_str);
+}
diff --git a/tests/all/serde/vec.rs b/tests/all/serde/vec.rs
new file mode 100644
index 0000000..3ad0ba5
--- /dev/null
+++ b/tests/all/serde/vec.rs
@@ -0,0 +1,57 @@
+#![cfg(all(feature = "collections", feature = "serde"))]
+
+use super::{assert_eq_json, Mixed, Test};
+
+use bumpalo::{vec, Bump};
+
+macro_rules! compare_std_vec {
+    (in $bump:ident; $($x:expr),+) => {{
+        let vec = vec![in &$bump; $($x),+];
+        let std_vec = std::vec![$($x),+];
+        (vec, std_vec)
+    }}
+}
+
+#[test]
+fn test_vec_serializes_str() {
+    let bump = Bump::new();
+    let (vec, std_vec) = compare_std_vec![in bump; "hello", "world"];
+    assert_eq_json!(vec, std_vec);
+    let de: std::vec::Vec<std::string::String> =
+        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
+    assert_eq!(de, std_vec);
+}
+
+#[test]
+fn test_vec_serializes_f32() {
+    let bump = Bump::new();
+    let (vec, std_vec) = compare_std_vec![in bump; 1.5707964, 3.1415927];
+    assert_eq_json!(vec, std_vec);
+    let de: std::vec::Vec<f32> =
+        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
+    assert_eq!(de, std_vec);
+}
+
+#[test]
+fn test_vec_serializes_complex() {
+    let bump = Bump::new();
+    let (vec, std_vec) = compare_std_vec![
+        in bump;
+        Mixed {
+            i: 8,
+            s: "a".into(),
+            o: None,
+            e: Test::Second,
+        },
+        Mixed {
+            i: 8,
+            s: "b".into(),
+            o: Some("some".into()),
+            e: Test::First,
+        }
+    ];
+    assert_eq_json!(vec, std_vec);
+    let de: std::vec::Vec<Mixed> =
+        serde_json::from_str(&serde_json::to_string(&vec).unwrap()).unwrap();
+    assert_eq!(de, std_vec);
+}