Skip to main content

oxc_allocator/
allocator.rs

1use std::{
2    alloc::Layout,
3    ptr::{self, NonNull},
4    slice, str,
5};
6
7use crate::arena::Arena;
8
9use oxc_data_structures::assert_unchecked;
10
11/// A bump-allocated memory arena.
12///
13/// # Anatomy of an Allocator
14///
15/// [`Allocator`] is flexibly sized. It grows as required as you allocate data into it.
16///
17/// To do that, an [`Allocator`] consists of multiple memory chunks.
18///
19/// [`Allocator::new`] creates a new allocator without any chunks. When you first allocate an object
20/// into it, it will lazily create an initial chunk, the size of which is determined by the size of that
21/// first allocation.
22///
23/// As more data is allocated into the [`Allocator`], it will likely run out of capacity. At that point,
24/// a new memory chunk is added, and further allocations will use this new chunk (until it too runs out
25/// of capacity, and *another* chunk is added).
26///
27/// The data from the 1st chunk is not copied into the 2nd one. It stays where it is, which means
28/// `&` or `&mut` references to data in the first chunk remain valid. This is unlike e.g. `Vec` which
29/// copies all existing data when it grows.
30///
31/// Each chunk is at least double the size of the last one, so growth in capacity is exponential.
32///
33/// [`Allocator::reset`] keeps only the last chunk (the biggest one), and discards any other chunks,
34/// returning their memory to the global allocator. The last chunk has its cursor rewound back to
35/// the start, so it's empty, ready to be re-used for allocating more data.
36///
37/// # Recycling allocators
38///
39/// For good performance, it's ideal to create an [`Allocator`], and re-use it over and over, rather than
40/// repeatedly creating and dropping [`Allocator`]s.
41///
42/// ```
43/// // This is good!
44/// use oxc_allocator::Allocator;
45/// let mut allocator = Allocator::new();
46///
47/// # fn do_stuff(_n: usize, _allocator: &Allocator) {}
48/// for i in 0..100 {
49///     do_stuff(i, &allocator);
50///     // Reset the allocator, freeing the memory used by `do_stuff`
51///     allocator.reset();
52/// }
53/// ```
54///
55/// ```
56/// // DON'T DO THIS!
57/// # use oxc_allocator::Allocator;
58/// # fn do_stuff(_n: usize, _allocator: &Allocator) {}
59/// for i in 0..100 {
60///     let allocator = Allocator::new();
61///     do_stuff(i, &allocator);
62/// }
63/// ```
64///
65/// ```
66/// // DON'T DO THIS EITHER!
67/// # use oxc_allocator::Allocator;
68/// # let allocator = Allocator::new();
69/// # fn do_stuff(_n: usize, _allocator: &Allocator) {}
70/// for i in 0..100 {
71///     do_stuff(i, &allocator);
72///     // We haven't reset the allocator, so we haven't freed the memory used by `do_stuff`.
73///     // The allocator will grow and grow, consuming more and more memory.
74/// }
75/// ```
76///
77/// ## Why is re-using an [`Allocator`] good for performance?
78///
79/// 3 reasons:
80///
81/// #### 1. Avoid expensive system calls
82///
83/// Creating an [`Allocator`] is a fairly expensive operation as it involves a call into global allocator,
84/// which in turn will likely make a system call. Ditto when the [`Allocator`] is dropped.
85/// Re-using an existing [`Allocator`] avoids these costs.
86///
87/// #### 2. CPU cache
88///
89/// Re-using an existing allocator means you're re-using the same block of memory. If that memory was
90/// recently accessed, it's likely to be warm in the CPU cache, so memory accesses will be much faster
91/// than accessing "cold" sections of main memory.
92///
93/// This can have a very significant positive impact on performance.
94///
95/// #### 3. Capacity stabilization
96///
97/// The most efficient [`Allocator`] is one with only 1 chunk which has sufficient capacity for
98/// everything you're going to allocate into it.
99///
100/// Why?
101///
102/// 1. Every allocation will occur without the allocator needing to grow.
103///
104/// 2. This makes the "is there sufficient capacity to allocate this?" check in [`alloc`] completely
105///    predictable (the answer is always "yes"). The CPU's branch predictor swiftly learns this,
106///    speeding up operation.
107///
108/// 3. When the [`Allocator`] is reset, there are no excess chunks to discard, so no system calls.
109///
110/// Because [`reset`] keeps only the biggest chunk (see above), re-using the same [`Allocator`]
111/// for multiple similar workloads will result in the [`Allocator`] swiftly stabilizing at a capacity
112/// which is sufficient to service those workloads with a single chunk.
113///
114/// If workload is completely uniform, it reaches stable state on the 3rd round.
115///
116/// ```no_run
117/// # // This isn't run as a doctest. It's much too slow.
118/// # use oxc_allocator::Allocator;
119/// let mut allocator = Allocator::new();
120///
121/// fn workload(allocator: &Allocator) {
122///     // Allocate 4 MB of data in small chunks
123///     for i in 0..1_000_000u32 {
124///         allocator.alloc(i);
125///     }
126/// }
127///
128/// // 1st round
129/// workload(&allocator);
130///
131/// // `allocator` has capacity for 4 MB data, but split into many chunks.
132/// // `reset` throws away all chunks except the last one which will be approx 2 MB.
133/// allocator.reset();
134///
135/// // 2nd round
136/// workload(&allocator);
137///
138/// // `workload` filled the 2 MB chunk, so a 2nd chunk was created of double the size (4 MB).
139/// // `reset` discards the smaller chunk, leaving only a single 4 MB chunk.
140/// allocator.reset();
141///
142/// // 3rd round
143/// // `allocator` now has sufficient capacity for all allocations in a single 4 MB chunk.
144/// workload(&allocator);
145///
146/// // `reset` has no chunks to discard. It keeps the single 4 MB chunk. No system calls.
147/// allocator.reset();
148///
149/// // More rounds
150/// // All serviced without needing to grow the allocator, and with no system calls.
151/// for _ in 0..100 {
152///   workload(&allocator);
153///   allocator.reset();
154/// }
155/// ```
156///
157/// # No `Drop`s
158///
159/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop).
160/// Memory is released in bulk when the allocator is dropped, without dropping the individual
161/// objects in the arena.
162///
163/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
164/// which own memory allocations outside the arena.
165///
166/// Static checks make this impossible to do. [`Allocator::alloc`], [`Box::new_in`], [`Vec::new_in`],
167/// [`HashMap::new_in`], and all other methods which store data in the arena will refuse to compile
168/// if called with a [`Drop`] type.
169///
170/// ```compile_fail
171/// use oxc_allocator::{Allocator, Box};
172/// let allocator = Allocator::new();
173///
174/// struct Foo {
175///     pub a: i32
176/// }
177///
178/// impl std::ops::Drop for Foo {
179///     fn drop(&mut self) {}
180/// }
181///
182/// // This will fail to compile because `Foo` implements `Drop`
183/// let foo = Box::new_in(Foo { a: 0 }, &allocator);
184/// ```
185///
186/// ```compile_fail
187/// use oxc_allocator::{Allocator, Box};
188/// let allocator = Allocator::new();
189///
190/// struct Bar {
191///     v: std::vec::Vec<u8>,
192/// }
193///
194/// // This will fail to compile because `Bar` contains a `std::vec::Vec`, and it implements `Drop`
195/// let bar = Box::new_in(Bar { v: vec![1, 2, 3] }, &allocator);
196/// ```
197///
198/// # Examples
199///
200/// Consumers of the [`oxc` umbrella crate](https://crates.io/crates/oxc) pass
201/// [`Allocator`] references to other tools.
202///
203/// ```ignore
204/// use oxc::{allocator::Allocator, parser::Parser, span::SourceType};
205///
206/// let allocator = Allocator::default();
207/// let parsed = Parser::new(&allocator, "let x = 1;", SourceType::default());
208/// assert!(parsed.diagnostics.is_empty());
209/// ```
210///
211/// [`reset`]: Allocator::reset
212/// [`alloc`]: Allocator::alloc
213/// [`Box::new_in`]: crate::Box::new_in
214/// [`Vec::new_in`]: crate::Vec::new_in
215/// [`HashMap::new_in`]: crate::HashMap::new_in
216#[derive(Default)]
217#[repr(transparent)]
218pub struct Allocator {
219    arena: Arena,
220}
221
222impl Allocator {
223    /// Create a new [`Allocator`] with no initial capacity.
224    ///
225    /// This method does not reserve any memory to back the allocator. Memory for allocator's initial
226    /// chunk will be reserved lazily, when you make the first allocation into this [`Allocator`]
227    /// (e.g. with [`Allocator::alloc`], [`Box::new_in`], [`Vec::new_in`], [`HashMap::new_in`]).
228    ///
229    /// If you can estimate the amount of memory the allocator will require to fit what you intend to
230    /// allocate into it, it is generally preferable to create that allocator with [`with_capacity`],
231    /// which reserves that amount of memory upfront. This will avoid further system calls to allocate
232    /// further chunks later on. This point is less important if you're re-using the allocator multiple
233    /// times.
234    ///
235    /// See [`Allocator`] docs for more information on efficient use of [`Allocator`].
236    ///
237    /// [`with_capacity`]: Allocator::with_capacity
238    /// [`Box::new_in`]: crate::Box::new_in
239    /// [`Vec::new_in`]: crate::Vec::new_in
240    /// [`HashMap::new_in`]: crate::HashMap::new_in
241    //
242    // `#[inline(always)]` because just delegates to `Arena` method
243    #[expect(clippy::inline_always)]
244    #[inline(always)]
245    pub fn new() -> Self {
246        Self { arena: Arena::new() }
247    }
248
249    /// Create a new [`Allocator`] with specified capacity.
250    ///
251    /// See [`Allocator`] docs for more information on efficient use of [`Allocator`].
252    //
253    // `#[inline(always)]` because just delegates to `Arena` method
254    #[expect(clippy::inline_always)]
255    #[inline(always)]
256    pub fn with_capacity(capacity: usize) -> Self {
257        Self { arena: Arena::with_capacity(capacity) }
258    }
259
260    /// Allocate an object in this [`Allocator`] and return an exclusive reference to it.
261    ///
262    /// # Panics
263    /// Panics if reserving space for `T` fails.
264    ///
265    /// # Examples
266    /// ```
267    /// use oxc_allocator::Allocator;
268    ///
269    /// let allocator = Allocator::default();
270    /// let x = allocator.alloc([1u8; 20]);
271    /// assert_eq!(x, &[1u8; 20]);
272    /// ```
273    //
274    // `#[inline(always)]` because this is a very hot path and `Arena::alloc` is a very small function.
275    // We always want it to be inlined.
276    #[expect(clippy::inline_always)]
277    #[inline(always)]
278    pub fn alloc<T>(&self, val: T) -> &mut T {
279        const { assert!(!std::mem::needs_drop::<T>(), "Cannot allocate Drop type in arena") };
280
281        self.arena.alloc(val)
282    }
283
284    /// Copy a string slice into this [`Allocator`] and return a reference to it.
285    ///
286    /// # Panics
287    /// Panics if reserving space for the string fails.
288    ///
289    /// # Examples
290    /// ```
291    /// use oxc_allocator::Allocator;
292    /// let allocator = Allocator::default();
293    /// let hello = allocator.alloc_str("hello world");
294    /// assert_eq!(hello, "hello world");
295    /// ```
296    //
297    // `#[inline(always)]` because this is a hot path and `Arena::alloc_str` is a very small function.
298    // We always want it to be inlined.
299    #[expect(clippy::inline_always)]
300    #[inline(always)]
301    pub fn alloc_str<'alloc>(&'alloc self, src: &str) -> &'alloc str {
302        self.arena.alloc_str(src)
303    }
304
305    /// `Copy` a slice into this [`Allocator`] and return an exclusive reference to the copy.
306    ///
307    /// # Panics
308    /// Panics if reserving space for the slice fails.
309    ///
310    /// # Examples
311    /// ```
312    /// use oxc_allocator::Allocator;
313    /// let allocator = Allocator::default();
314    /// let x = allocator.alloc_slice_copy(&[1, 2, 3]);
315    /// assert_eq!(x, &[1, 2, 3]);
316    /// ```
317    //
318    // `#[inline(always)]` because this is a hot path and `Arena::alloc_slice_copy` is a very small function.
319    // We always want it to be inlined.
320    #[expect(clippy::inline_always)]
321    #[inline(always)]
322    pub fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> &mut [T] {
323        self.arena.alloc_slice_copy(src)
324    }
325
326    /// Allocate space for an object with the given [`Layout`].
327    ///
328    /// The returned pointer points at uninitialized memory, and should be initialized with
329    /// [`std::ptr::write`].
330    ///
331    /// # Panics
332    ///
333    /// Panics if reserving space matching `layout` fails.
334    //
335    // `#[inline(always)]` because this is a hot path and `Arena::alloc_layout` is a very small function.
336    // We always want it to be inlined.
337    #[expect(clippy::inline_always)]
338    #[inline(always)]
339    pub fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
340        self.arena.alloc_layout(layout)
341    }
342
343    /// Create new `&str` from a fixed-size array of `&str`s concatenated together,
344    /// allocated in the given `allocator`.
345    ///
346    /// # Panics
347    ///
348    /// Panics if the sum of length of all strings exceeds `isize::MAX`.
349    ///
350    /// # Example
351    /// ```
352    /// use oxc_allocator::Allocator;
353    ///
354    /// let allocator = Allocator::new();
355    /// let s = allocator.alloc_concat_strs_array(["hello", " ", "world", "!"]);
356    /// assert_eq!(s, "hello world!");
357    /// ```
358    // `#[inline(always)]` because want compiler to be able to remove checked addition where some of
359    // `strings` are statically known.
360    #[expect(clippy::inline_always)]
361    #[inline(always)]
362    pub fn alloc_concat_strs_array<'a, const N: usize>(&'a self, strings: [&str; N]) -> &'a str {
363        // Calculate total length of all the strings concatenated.
364        //
365        // We have to use `checked_add` here to guard against additions wrapping around
366        // if some of the input `&str`s are very long, or there's many of them.
367        //
368        // However, `&str`s have max length of `isize::MAX`.
369        // https://users.rust-lang.org/t/does-str-reliably-have-length-isize-max/126777
370        // Use `assert_unchecked!` to communicate this invariant to compiler, which allows it to
371        // optimize out the overflow checks where some of `strings` are static, so their size is known.
372        //
373        // e.g. `allocator.from_strs_array_in(["__vite_ssr_import_", str, "__"])`, for example,
374        // requires no checks at all, because the static parts have total length of 20 bytes,
375        // and `str` has max length of `isize::MAX`. `isize::MAX as usize + 20` cannot overflow `usize`.
376        // Compiler can see that, and removes the overflow check.
377        // https://godbolt.org/z/MGh44Yz5d
378        #[expect(clippy::checked_conversions)]
379        let total_len = strings.iter().fold(0usize, |total_len, s| {
380            let len = s.len();
381            // SAFETY: `&str`s have maximum length of `isize::MAX`
382            unsafe { assert_unchecked!(len <= (isize::MAX as usize)) };
383            total_len.checked_add(len).unwrap()
384        });
385
386        if total_len == 0 {
387            return "";
388        }
389
390        assert!(
391            isize::try_from(total_len).is_ok(),
392            "attempted to create a string longer than `isize::MAX` bytes"
393        );
394
395        // Create actual `&str` in a separate function, to ensure that `alloc_concat_strs_array`
396        // is inlined, so that compiler has knowledge to remove the overflow checks above.
397        // When some of `strings` are static, this function is usually only a few instructions.
398        // Compiler can choose whether or not to inline `alloc_concat_strs_array_with_total_len_in`.
399        // SAFETY: `total_len` has been calculated correctly above.
400        // `total_len` is `<= isize::MAX`.
401        unsafe { self.alloc_concat_strs_array_with_total_len_in(strings, total_len) }
402    }
403
404    /// Create a new `&str` from a fixed-size array of `&str`s concatenated together,
405    /// allocated in the given `allocator`, with provided `total_len`.
406    ///
407    /// # SAFETY
408    /// * `total_len` must be the total length of all `strings` concatenated.
409    /// * `total_len` must be `<= isize::MAX`.
410    unsafe fn alloc_concat_strs_array_with_total_len_in<'a, const N: usize>(
411        &'a self,
412        strings: [&str; N],
413        total_len: usize,
414    ) -> &'a str {
415        // Allocate `total_len` bytes.
416        // SAFETY: Caller guarantees `total_len <= isize::MAX`.
417        let layout = unsafe { Layout::from_size_align_unchecked(total_len, 1) };
418        let start_ptr = self.alloc_layout(layout);
419
420        let mut end_ptr = start_ptr;
421        for str in strings {
422            let src_ptr = str.as_ptr();
423            let len = str.len();
424
425            // SAFETY:
426            // `src` is obtained from a `&str` with length `len`, so is valid for reading `len` bytes.
427            // `end_ptr` is within bounds of the allocation. So is `end_ptr + len`.
428            // `u8` has no alignment requirements, so `src_ptr` and `end_ptr` are sufficiently aligned.
429            // No overlapping, because we're copying from an existing `&str` to a newly allocated buffer.
430            unsafe { ptr::copy_nonoverlapping(src_ptr, end_ptr.as_ptr(), len) };
431
432            // SAFETY: We allocated sufficient capacity for all the strings concatenated.
433            // So `end_ptr.add(len)` cannot go out of bounds.
434            end_ptr = unsafe { end_ptr.add(len) };
435        }
436
437        debug_assert_eq!(end_ptr.as_ptr() as usize - start_ptr.as_ptr() as usize, total_len);
438
439        // SAFETY: We have allocated and filled `total_len` bytes starting at `start_ptr`.
440        // Concatenating multiple `&str`s results in a valid UTF-8 string.
441        unsafe {
442            let slice = slice::from_raw_parts(start_ptr.as_ptr(), total_len);
443            str::from_utf8_unchecked(slice)
444        }
445    }
446
447    /// Reset this allocator.
448    ///
449    /// Performs mass deallocation on everything allocated in this arena by resetting the pointer
450    /// into the underlying chunk of memory to the start of the chunk.
451    /// Does not run any `Drop` implementations on deallocated objects.
452    ///
453    /// If this arena has allocated multiple chunks to bump allocate into, then the excess chunks
454    /// are returned to the global allocator.
455    ///
456    /// # Examples
457    /// ```
458    /// use oxc_allocator::Allocator;
459    ///
460    /// let mut allocator = Allocator::default();
461    ///
462    /// // Allocate a bunch of things.
463    /// {
464    ///     for i in 0..100 {
465    ///         allocator.alloc(i);
466    ///     }
467    /// }
468    ///
469    /// // Reset the arena.
470    /// allocator.reset();
471    ///
472    /// // Allocate some new things in the space previously occupied by the
473    /// // original things.
474    /// for j in 200..400 {
475    ///     allocator.alloc(j);
476    /// }
477    /// ```
478    //
479    // `#[inline(always)]` because just delegates to `Arena` method
480    #[expect(clippy::inline_always)]
481    #[inline(always)]
482    pub fn reset(&mut self) {
483        self.arena.reset();
484    }
485
486    /// Calculate the total capacity of this [`Allocator`] including all chunks, in bytes.
487    ///
488    /// Note: This is the total amount of memory the [`Allocator`] owns NOT the total size of data
489    /// that's been allocated in it. If you want the latter, use [`used_bytes`] instead.
490    ///
491    /// # Examples
492    /// ```
493    /// use oxc_allocator::Allocator;
494    ///
495    /// let capacity = 64 * 1024; // 64 KiB
496    /// let mut allocator = Allocator::with_capacity(capacity);
497    /// allocator.alloc(123u64); // 8 bytes
498    ///
499    /// // Result is the capacity (64 KiB), not the size of allocated data (8 bytes).
500    /// // `Allocator::with_capacity` may allocate a bit more than requested.
501    /// assert!(allocator.capacity() >= capacity);
502    /// ```
503    ///
504    /// [`used_bytes`]: Allocator::used_bytes
505    //
506    // `#[inline(always)]` because just delegates to `Arena` method
507    #[expect(clippy::inline_always)]
508    #[inline(always)]
509    pub fn capacity(&self) -> usize {
510        self.arena.allocated_bytes()
511    }
512
513    /// Calculate the total size of data used in this [`Allocator`], in bytes.
514    ///
515    /// This is the total amount of memory that has been *used* in the [`Allocator`], NOT the amount of
516    /// memory the [`Allocator`] owns. If you want the latter, use [`capacity`] instead.
517    ///
518    /// The result includes:
519    ///
520    /// 1. Padding bytes between objects which have been allocated to preserve alignment of types
521    ///    where they have different alignments or have larger-than-typical alignment.
522    /// 2. Excess capacity in [`Vec`]s, [`StringBuilder`]s and [`HashMap`]s.
523    /// 3. Objects which were allocated but later dropped. [`Allocator`] does not re-use allocations,
524    ///    so anything which is allocated into arena continues to take up "dead space", even after it's
525    ///    no longer referenced anywhere.
526    /// 4. "Dead space" left over where a [`Vec`], [`StringBuilder`] or [`HashMap`] has grown and had to
527    ///    make a new allocation to accommodate its new larger size. Its old allocation continues to
528    ///    take up "dead" space in the allocator, unless it was the most recent allocation.
529    ///
530    /// In practice, this almost always means that the result returned from this function will be an
531    /// over-estimate vs the amount of "live" data in the arena.
532    ///
533    /// However, if you are using the result of this method to create a new `Allocator` to clone
534    /// an AST into, it is theoretically possible (though very unlikely) that it may be a slight
535    /// under-estimate of the capacity required in new allocator to clone the AST into, depending
536    /// on the order that `&str`s were allocated into arena in parser vs the order they get allocated
537    /// during cloning. The order allocations are made in affects the amount of padding bytes required.
538    ///
539    /// # Examples
540    /// ```
541    /// use oxc_allocator::{Allocator, Vec};
542    ///
543    /// let capacity = 64 * 1024; // 64 KiB
544    /// let mut allocator = Allocator::with_capacity(capacity);
545    ///
546    /// allocator.alloc(1u8); // 1 byte with alignment 1
547    /// allocator.alloc(2u8); // 1 byte with alignment 1
548    /// allocator.alloc(3u64); // 8 bytes with alignment 8
549    ///
550    /// // Only 10 bytes were allocated, but 16 bytes were used, in order to align `3u64` on 8
551    /// assert_eq!(allocator.used_bytes(), 16);
552    ///
553    /// allocator.reset();
554    ///
555    /// let mut vec = Vec::<u64>::with_capacity_in(2, &&allocator);
556    ///
557    /// // Allocate something else, so `vec`'s allocation is not the most recent
558    /// allocator.alloc(123u64);
559    ///
560    /// // `vec` has to grow beyond it's initial capacity
561    /// vec.extend([1, 2, 3, 4]);
562    ///
563    /// // `vec` takes up 32 bytes, and `123u64` takes up 8 bytes = 40 total.
564    /// // But there's an additional 16 bytes consumed for `vec`'s original capacity of 2,
565    /// // which is still using up space
566    /// assert_eq!(allocator.used_bytes(), 56);
567    /// ```
568    ///
569    /// [`capacity`]: Allocator::capacity
570    /// [`Vec`]: crate::Vec
571    /// [`StringBuilder`]: crate::StringBuilder
572    /// [`HashMap`]: crate::HashMap
573    //
574    // `#[inline(always)]` because just delegates to `Arena` method
575    #[expect(clippy::inline_always)]
576    #[inline(always)]
577    pub fn used_bytes(&self) -> usize {
578        self.arena.used_bytes()
579    }
580
581    /// Get inner [`Arena`].
582    ///
583    /// This method is not public. We don't want to expose `Arena` to user.
584    /// The inner `Arena` is an internal implementation detail.
585    //
586    // `#[inline(always)]` because it's a no-op
587    #[expect(clippy::inline_always)]
588    #[inline(always)]
589    pub(crate) fn arena(&self) -> &Arena {
590        &self.arena
591    }
592
593    /// Create [`Allocator`] from an [`Arena`].
594    ///
595    /// This method is not public. Only used by [`Allocator::from_raw_parts`].
596    //
597    // `#[inline(always)]` because it's a no-op
598    #[cfg(feature = "from_raw_parts")]
599    #[expect(clippy::inline_always)]
600    #[inline(always)]
601    pub(crate) fn from_arena(arena: Arena) -> Self {
602        Self { arena }
603    }
604}
605
606#[cfg(test)]
607mod test {
608    use super::Allocator;
609
610    #[test]
611    fn test_api() {
612        let mut allocator = Allocator::default();
613        {
614            let array = allocator.alloc([123; 10]);
615            assert_eq!(array, &[123; 10]);
616            let str = allocator.alloc_str("hello");
617            assert_eq!(str, "hello");
618        }
619        allocator.reset();
620    }
621
622    #[test]
623    fn string_from_array_len_1() {
624        let allocator = Allocator::default();
625        let s = allocator.alloc_concat_strs_array(["hello"]);
626        assert_eq!(s, "hello");
627    }
628
629    #[test]
630    fn string_from_array_len_2() {
631        let allocator = Allocator::default();
632        let s = allocator.alloc_concat_strs_array(["hello", "world!"]);
633        assert_eq!(s, "helloworld!");
634    }
635
636    #[test]
637    fn string_from_array_len_3() {
638        let hello = "hello";
639        let world = std::string::String::from("world");
640        let allocator = Allocator::default();
641        let s = allocator.alloc_concat_strs_array([hello, &world, "!"]);
642        assert_eq!(s, "helloworld!");
643    }
644
645    #[test]
646    fn string_from_empty_array() {
647        let allocator = Allocator::default();
648        let s = allocator.alloc_concat_strs_array([]);
649        assert_eq!(s, "");
650    }
651
652    #[test]
653    fn string_from_array_of_empty_strs() {
654        let allocator = Allocator::default();
655        let s = allocator.alloc_concat_strs_array(["", "", ""]);
656        assert_eq!(s, "");
657    }
658
659    #[test]
660    fn string_from_array_containing_some_empty_strs() {
661        let allocator = Allocator::default();
662        let s = allocator.alloc_concat_strs_array(["", "hello", ""]);
663        assert_eq!(s, "hello");
664    }
665}