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}