stack_arena/
object_stack.rs

1use std::{alloc::Layout, ptr::NonNull};
2
3use crate::{Allocator, StackArena};
4
5/// A high-level stack-based object builder that uses `StackArena` for memory management.
6///
7/// `ObjectStack` provides a more user-friendly interface on top of [`StackArena`](crate::StackArena) for
8/// building and managing objects in a stack-like fashion. It supports incremental
9/// object construction through the `extend` and `finish` methods, as well as
10/// direct formatting through the `std::fmt::Write` trait.
11///
12/// # Features
13///
14/// - Push complete objects onto the stack
15/// - Build objects incrementally using `extend` and `finish` methods
16/// - Implements `std::fmt::Write` for string formatting directly into objects
17/// - Stack-like (LIFO) allocation and deallocation pattern
18/// - Memory-efficient with minimal overhead
19/// - Automatic memory management with chunk reuse
20///
21/// # Use Cases
22///
23/// `ObjectStack` is particularly useful for:
24///
25/// - String building and text processing
26/// - Constructing complex objects incrementally
27/// - Serialization operations
28/// - Any scenario where objects need to be built in stages
29///
30/// # Examples
31///
32/// ```
33/// use stack_arena::ObjectStack;
34/// use std::fmt::Write;
35///
36/// let mut stack = ObjectStack::new();
37///
38/// // Push a complete object
39/// stack.push(b"hello");
40///
41/// // Build an object incrementally
42/// stack.extend("world ");
43/// write!(&mut stack, "from {}", "Rust").unwrap();
44/// let ptr = stack.finish();
45///
46/// // Access the object
47/// let message = unsafe { std::str::from_utf8_unchecked(ptr.as_ref()) };
48/// assert_eq!(message, "world from Rust");
49///
50/// // Pop the object when done
51/// stack.pop();
52/// ```
53#[derive(Debug)]
54pub struct ObjectStack {
55    arena: StackArena,
56    partial: bool,
57}
58
59impl ObjectStack {
60    /// Creates a new empty `ObjectStack` with a default initial capacity.
61    ///
62    /// The initial chunk size is 1024 bytes, managed by the underlying `StackArena`.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use stack_arena::ObjectStack;
68    ///
69    /// let stack = ObjectStack::new();
70    /// assert!(stack.is_empty());
71    /// ```
72    #[inline]
73    pub fn new() -> Self {
74        Self {
75            arena: StackArena::new(),
76            partial: false,
77        }
78    }
79
80    /// Returns the number of objects currently on the stack.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use stack_arena::ObjectStack;
86    ///
87    /// let mut stack = ObjectStack::new();
88    /// assert_eq!(stack.len(), 0);
89    ///
90    /// stack.push(b"hello");
91    /// assert_eq!(stack.len(), 1);
92    /// ```
93    #[inline]
94    pub fn len(&self) -> usize {
95        self.arena.len()
96    }
97
98    /// Returns `true` if the stack contains no objects.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use stack_arena::ObjectStack;
104    ///
105    /// let mut stack = ObjectStack::new();
106    /// assert!(stack.is_empty());
107    ///
108    /// stack.push(b"hello");
109    /// assert!(!stack.is_empty());
110    /// ```
111    #[inline]
112    pub fn is_empty(&self) -> bool {
113        self.arena.is_empty()
114    }
115
116    /// Pushes a complete object onto the stack.
117    ///
118    /// This method allocates memory for the object, copies the data,
119    /// and returns a pointer to the allocated memory.
120    ///
121    /// # Parameters
122    ///
123    /// * `object` - The data to push onto the stack, which can be any type
124    ///   that can be converted to a byte slice.
125    ///
126    /// # Returns
127    ///
128    /// A non-null pointer to the allocated memory containing the data.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use stack_arena::ObjectStack;
134    ///
135    /// let mut stack = ObjectStack::new();
136    /// let ptr = stack.push(b"hello world");
137    /// // The pointer is valid until the object is popped or freed
138    /// ```
139    #[inline]
140    pub fn push<P: AsRef<[u8]>>(&mut self, object: P) -> NonNull<[u8]> {
141        let data = object.as_ref();
142        let layout = Layout::for_value(data);
143        let ptr = unsafe { self.arena.allocate(layout).unwrap() };
144        unsafe {
145            ptr.cast().copy_from_nonoverlapping(
146                NonNull::new_unchecked(data.as_ptr().cast_mut()),
147                data.len(),
148            )
149        };
150        ptr
151    }
152
153    /// Removes the most recently pushed object from the stack.
154    ///
155    /// This method follows the LIFO (Last-In-First-Out) principle.
156    /// After popping, any pointers to the popped object become invalid.
157    ///
158    /// # Panics
159    ///
160    /// Panics if the stack is empty or if there is a partial object
161    /// being built (i.e., if `extend` has been called but `finish` has not).
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use stack_arena::ObjectStack;
167    ///
168    /// let mut stack = ObjectStack::new();
169    /// stack.push(b"hello");
170    /// stack.push(b"world");
171    /// assert_eq!(stack.len(), 2);
172    ///
173    /// stack.pop();
174    /// assert_eq!(stack.len(), 1);
175    /// ```
176    #[inline]
177    pub fn pop(&mut self) {
178        self.arena.pop();
179        self.partial = false;
180    }
181
182    /// Extends the current object being built with additional data.
183    ///
184    /// This method is used for incrementally building objects. Multiple calls to
185    /// `extend` can be made before finalizing the object with `finish`. This method
186    /// **only supports extending the last allocation** (following LIFO pattern),
187    /// as it uses the underlying arena's grow functionality.
188    ///
189    /// # Parameters
190    ///
191    /// * `value` - The data to append to the current object, which can be any type
192    ///   that can be converted to a byte slice.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use stack_arena::ObjectStack;
198    ///
199    /// let mut stack = ObjectStack::new();
200    /// stack.extend("Hello, ");
201    /// stack.extend("world!");
202    /// let ptr = stack.finish();
203    /// // ptr now points to "Hello, world!"
204    /// ```
205    #[inline]
206    pub fn extend<P: AsRef<[u8]>>(&mut self, value: P) {
207        let data = value.as_ref();
208        if self.partial {
209            let partial_object = self.arena.top().unwrap();
210            let old_layout = Layout::for_value(unsafe { partial_object.as_ref() });
211            let new_layout = unsafe {
212                Layout::from_size_align_unchecked(
213                    old_layout.size() + data.len(),
214                    old_layout.align(),
215                )
216            };
217            let store = unsafe {
218                self.arena
219                    .grow(partial_object.cast(), old_layout, new_layout)
220            }
221            .unwrap();
222            unsafe {
223                store
224                    .cast::<u8>()
225                    .add(old_layout.size())
226                    .copy_from_nonoverlapping(
227                        NonNull::new_unchecked(data.as_ptr().cast_mut()),
228                        data.len(),
229                    );
230            }
231        } else {
232            let store = unsafe { self.arena.allocate(Layout::for_value(data)) }.unwrap();
233            unsafe {
234                store.cast::<u8>().copy_from_nonoverlapping(
235                    NonNull::new_unchecked(data.as_ptr().cast_mut()),
236                    data.len(),
237                )
238            };
239        }
240        self.partial = true;
241    }
242
243    /// Finalizes the current object being built and adds it to the stack.
244    ///
245    /// This method should be called after one or more calls to `extend` to
246    /// finalize the object and make it available on the stack.
247    ///
248    /// # Returns
249    ///
250    /// A non-null pointer to the finalized object.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use stack_arena::ObjectStack;
256    ///
257    /// let mut stack = ObjectStack::new();
258    /// stack.extend("Hello");
259    /// stack.extend(" world");
260    /// let ptr = stack.finish();
261    /// // ptr now points to "Hello world"
262    /// ```
263    #[inline]
264    pub fn finish(&mut self) -> NonNull<[u8]> {
265        debug_assert!(self.partial);
266        self.partial = false;
267        self.arena.top().unwrap()
268    }
269
270    /// Rolls back to a specific object, freeing it and all objects allocated after it.
271    ///
272    /// This method allows for rolling back to a specific point in the allocation
273    /// history by providing a reference to an object on the stack.
274    ///
275    /// # Parameters
276    ///
277    /// * `data` - A reference to the object to free, along with all objects
278    ///   allocated after it.
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use stack_arena::ObjectStack;
284    ///
285    /// let mut stack = ObjectStack::new();
286    /// stack.push(b"first");
287    /// let second = stack.push(b"second");
288    /// stack.push(b"third");
289    ///
290    /// // Free "second" and "third"
291    /// stack.rollback(unsafe { second.as_ref() });
292    /// assert_eq!(stack.len(), 1); // Only "first" remains
293    /// ```
294    #[inline]
295    pub fn rollback(&mut self, data: &[u8]) {
296        let data = data.as_ref();
297        unsafe {
298            self.arena.deallocate(
299                NonNull::new_unchecked(data.as_ptr().cast_mut()),
300                Layout::for_value(data),
301            )
302        };
303    }
304}
305
306/// Implementation of the `std::fmt::Write` trait for `ObjectStack`.
307///
308/// This allows using the `write!` macro and other formatting utilities
309/// to write formatted text directly into the object being built.
310///
311/// # Examples
312///
313/// ```
314/// use std::fmt::Write;
315/// use stack_arena::ObjectStack;
316///
317/// let mut stack = ObjectStack::new();
318/// write!(&mut stack, "Hello, {}!", "world").unwrap();
319/// let formatted = stack.finish();
320/// // formatted now points to "Hello, world!"
321/// ```
322impl std::fmt::Write for ObjectStack {
323    /// Writes a string into the arena.
324    ///
325    /// This method extends the current object with the given string.
326    #[inline]
327    fn write_str(&mut self, s: &str) -> std::fmt::Result {
328        self.extend(s);
329        Ok(())
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use std::fmt::Write;
337
338    #[test]
339    fn test_lifecycle() {
340        let mut stack = ObjectStack::new();
341        write!(&mut stack, "ab").expect("write");
342        let s = "c";
343        stack.extend(s);
344        let p = unsafe { stack.finish().as_ref() };
345        assert_eq!(p, b"abc");
346    }
347
348    #[test]
349    fn test_push_pop() {
350        let mut stack = ObjectStack::new();
351
352        // Test push
353        stack.push(b"hello");
354        assert_eq!(stack.len(), 1);
355        assert!(!stack.is_empty());
356
357        // Test push multiple items
358        stack.push(b"world");
359        assert_eq!(stack.len(), 2);
360
361        // Test pop
362        stack.pop();
363        assert_eq!(stack.len(), 1);
364
365        // Test pop to empty
366        stack.pop();
367        assert_eq!(stack.len(), 0);
368        assert!(stack.is_empty());
369    }
370
371    #[test]
372    fn test_extend_small_data() {
373        let mut stack = ObjectStack::new();
374
375        // Extend with data smaller than chunk capacity
376        stack.extend(b"hello");
377
378        // Extend again with small data
379        stack.extend(b" world");
380
381        // Finish and verify
382        let data = unsafe { stack.finish().as_ref() };
383        assert_eq!(data, b"hello world");
384    }
385
386    #[test]
387    fn test_extend_large_data() {
388        let mut stack = ObjectStack::new();
389
390        // Extend with data larger than chunk capacity
391        let large_data = vec![b'x'; 20];
392        stack.extend(&large_data);
393
394        // Finish and verify
395        let data = unsafe { stack.finish().as_ref() };
396        assert_eq!(data, &large_data[..]);
397    }
398
399    #[test]
400    fn test_extend_after_finish() {
401        let mut stack = ObjectStack::new();
402
403        // First object
404        stack.extend("first");
405        let first = unsafe { stack.finish().as_ref() };
406        assert_eq!(first, b"first");
407        // Second object
408        stack.extend(b"second");
409        let second = unsafe { stack.finish().as_ref() };
410
411        assert_eq!(second, b"second");
412
413        // Verify both objects are still valid
414        assert_eq!(first, b"first");
415        assert_eq!(second, b"second");
416    }
417
418    #[test]
419    fn test_free() {
420        let mut stack = ObjectStack::new();
421
422        // Create multiple objects
423        stack.push(b"first");
424        stack.extend(b"second");
425        let second = unsafe { stack.finish().as_ref() };
426        stack.extend(b"third");
427        let _third = unsafe { stack.finish().as_ref() };
428
429        // Free up to second object
430        stack.rollback(second);
431
432        // Verify stack state
433        assert_eq!(stack.len(), 1); // Only "first" remains
434
435        // Add a new object
436        stack.extend(b"fourth");
437        let fourth = unsafe { stack.finish().as_ref() };
438        assert_eq!(fourth, b"fourth");
439
440        // Verify stack state
441        assert_eq!(stack.len(), 2); // "first" and "fourth"
442    }
443
444    #[test]
445    fn test_write_trait() {
446        let mut stack = ObjectStack::new();
447
448        // Test write_str via the Write trait
449        write!(&mut stack, "Hello, {}!", "world").unwrap();
450        // Finish and verify
451        let data = unsafe { stack.finish().as_ref() };
452        assert_eq!(data, b"Hello, world!");
453    }
454
455    #[test]
456    fn test_empty_data() {
457        let mut stack = ObjectStack::new();
458
459        // Test with empty data
460        stack.extend(b"");
461        let data = unsafe { stack.finish().as_ref() };
462        assert_eq!(data, b"");
463
464        // Test push with empty data
465        stack.push(b"");
466        assert_eq!(stack.len(), 2);
467    }
468
469    #[test]
470    fn test_multiple_operations() {
471        let mut stack = ObjectStack::new();
472
473        // Mix of operations
474        stack.push(b"item1");
475        stack.extend(b"item2-part1");
476        stack.extend(b"-part2");
477        let item2 = unsafe { stack.finish().as_ref() };
478        write!(&mut stack, "item3").unwrap();
479        let item3 = unsafe { stack.finish().as_ref() };
480
481        // Verify
482        assert_eq!(item2, b"item2-part1-part2");
483        assert_eq!(item3, b"item3");
484        assert_eq!(stack.len(), 3);
485
486        // Pop and verify
487        stack.pop();
488        assert_eq!(stack.len(), 2);
489    }
490
491    #[test]
492    fn test_extend_exact_capacity() {
493        let mut stack = ObjectStack::new();
494
495        // Fill exactly to capacity
496        let data = vec![b'x'; 10]; // Same as chunk_size
497        stack.extend(&data);
498
499        // Add more data to trigger new allocation
500        stack.extend(b"more");
501
502        // Finish and verify
503        let result = unsafe { stack.finish().as_ref() };
504        let mut expected = data.clone();
505        expected.extend_from_slice(b"more");
506        assert_eq!(result, expected.as_slice());
507    }
508
509    #[test]
510    fn test_free_all() {
511        let mut stack = ObjectStack::new();
512
513        // Create multiple objects
514        let first = stack.push(b"first");
515        stack.extend(b"second");
516        let _second = unsafe { stack.finish().as_ref() };
517
518        // Free all objects by freeing the first one
519        stack.rollback(unsafe { first.as_ref() });
520
521        // Verify stack is empty
522        assert_eq!(stack.len(), 0);
523        assert!(stack.is_empty());
524    }
525
526    #[test]
527    #[should_panic]
528    fn test_free_nonexistent() {
529        let mut stack = ObjectStack::new();
530
531        // Create an object
532        stack.push(b"object");
533        assert_eq!(stack.len(), 1);
534
535        // Try to free a non-existent object
536        let dummy = b"nonexistent";
537        stack.rollback(dummy);
538
539        // Stack should remain unchanged
540        assert_eq!(stack.len(), 1);
541    }
542
543    #[test]
544    fn test_cross_chunk_allocation_deallocation() {
545        // Create an ObjectStack with a custom StackArena that has a very small chunk size
546        let mut stack = ObjectStack {
547            arena: StackArena::with_chunk_size(8),
548            partial: false,
549        };
550
551        let small1 = stack.push("small1");
552        stack.push("small2");
553        assert_eq!(stack.len(), 2);
554
555        // Pop the second object to maintain LIFO order
556        stack.pop();
557        assert_eq!(stack.len(), 1);
558
559        let large = "start- middle- this-is-a-longer-string-to-trigger-new-chunk";
560        for part in large.split(' ') {
561            stack.extend(part);
562        }
563        assert_eq!(stack.len(), 2);
564
565        // Finish the object
566        let large = stack.finish();
567        assert_eq!(stack.len(), 2);
568        unsafe {
569            assert_eq!(large.as_ref(), large.as_ref());
570        }
571
572        // Verify data integrity
573        assert_eq!(unsafe { small1.as_ref() }, b"small1");
574
575        // Pop in LIFO order
576        stack.pop(); // Pop large
577        assert_eq!(stack.len(), 1);
578
579        stack.pop(); // Pop small1
580        assert!(stack.is_empty());
581
582        // Build a new object with a single extend
583        stack.extend("single-extend-object");
584
585        // Finish the object
586        let single = stack.finish();
587
588        // Verify the object
589        assert_eq!(unsafe { single.as_ref() }, b"single-extend-object");
590
591        // Clean up
592        stack.pop();
593        assert_eq!(stack.len(), 0);
594    }
595}