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 objects onto the stack with move or copy semantics
15/// - Push byte slices onto the stack
16/// - Build objects incrementally using `extend` and `finish` methods
17/// - Implements `std::fmt::Write` for string formatting directly into objects
18/// - Stack-like (LIFO) allocation and deallocation pattern
19/// - Memory-efficient with minimal overhead
20/// - Automatic memory management with chunk reuse
21///
22/// # Use Cases
23///
24/// `ObjectStack` is particularly useful for:
25///
26/// - String building and text processing
27/// - Constructing complex objects incrementally
28/// - Serialization operations
29/// - Any scenario where objects need to be built in stages
30///
31/// # Examples
32///
33/// ```
34/// use stack_arena::ObjectStack;
35/// use std::fmt::Write;
36///
37/// let mut stack = ObjectStack::new();
38///
39/// // Push bytes onto the stack
40/// stack.push_bytes(b"hello");
41///
42/// // Push a value with move semantics
43/// let string = String::from("world");
44/// stack.push(string); // string is moved
45///
46/// // Push a value with copy semantics
47/// let value = 42;
48/// stack.push_copy(&value);
49///
50/// // Build an object incrementally
51/// stack.extend("incremental ");
52/// write!(&mut stack, "from {}", "Rust").unwrap();
53/// let ptr = stack.finish();
54///
55/// // Access the object
56/// let message = unsafe { std::str::from_utf8_unchecked(ptr.as_ref()) };
57/// assert_eq!(message, "incremental from Rust");
58///
59/// // Pop the object when done
60/// stack.pop();
61/// ```
62#[derive(Debug)]
63pub struct ObjectStack {
64    arena: StackArena,
65    partial: bool,
66}
67
68impl ObjectStack {
69    /// Creates a new empty `ObjectStack` with a default initial capacity.
70    ///
71    /// The initial chunk size is 1024 bytes, managed by the underlying `StackArena`.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use stack_arena::ObjectStack;
77    ///
78    /// let stack = ObjectStack::new();
79    /// assert!(stack.is_empty());
80    /// ```
81    #[inline]
82    pub fn new() -> Self {
83        Self {
84            arena: StackArena::new(),
85            partial: false,
86        }
87    }
88
89    /// Returns the number of objects currently on the stack.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use stack_arena::ObjectStack;
95    ///
96    /// let mut stack = ObjectStack::new();
97    /// assert_eq!(stack.len(), 0);
98    ///
99    /// stack.push_bytes(b"hello");
100    /// assert_eq!(stack.len(), 1);
101    /// ```
102    #[inline]
103    pub fn len(&self) -> usize {
104        self.arena.len()
105    }
106
107    /// Returns `true` if the stack contains no objects.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use stack_arena::ObjectStack;
113    ///
114    /// let mut stack = ObjectStack::new();
115    /// assert!(stack.is_empty());
116    ///
117    /// stack.push_bytes(b"hello");
118    /// assert!(!stack.is_empty());
119    /// ```
120    #[inline]
121    pub fn is_empty(&self) -> bool {
122        self.arena.is_empty()
123    }
124
125    /// Pushes a byte slice onto the stack.
126    ///
127    /// This method allocates memory for the byte slice, copies the data,
128    /// and returns a pointer to the allocated memory.
129    ///
130    /// # Parameters
131    ///
132    /// * `object` - The data to push onto the stack, which can be any type
133    ///   that can be converted to a byte slice.
134    ///
135    /// # Returns
136    ///
137    /// A non-null pointer to the allocated memory containing the data.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use stack_arena::ObjectStack;
143    ///
144    /// let mut stack = ObjectStack::new();
145    /// let ptr = stack.push_bytes(b"hello world");
146    /// // The pointer is valid until the object is popped or freed
147    /// ```
148    #[inline]
149    pub fn push_bytes<P: AsRef<[u8]>>(&mut self, object: P) -> NonNull<[u8]> {
150        let data = object.as_ref();
151        let layout = Layout::for_value(data);
152        let ptr = unsafe { self.arena.allocate(layout).unwrap() };
153        unsafe {
154            ptr.cast().copy_from_nonoverlapping(
155                NonNull::new_unchecked(data.as_ptr().cast_mut()),
156                data.len(),
157            )
158        };
159        ptr
160    }
161
162    /// Pushes a copy of a value onto the stack.
163    ///
164    /// This method takes a reference to a value that implements the `Copy` trait,
165    /// makes a bitwise copy of it, and pushes it onto the stack.
166    ///
167    /// # Parameters
168    ///
169    /// * `object` - A reference to the value to copy onto the stack.
170    ///
171    /// # Returns
172    ///
173    /// A non-null pointer to the allocated memory containing the copied value.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// use stack_arena::ObjectStack;
179    ///
180    /// let mut stack = ObjectStack::new();
181    ///
182    /// // Copy a primitive value
183    /// let value = 42;
184    /// let ptr = stack.push_copy(&value);
185    ///
186    /// // Copy a struct that implements Copy
187    /// #[derive(Copy, Clone)]
188    /// struct Point { x: i32, y: i32 }
189    ///
190    /// let point = Point { x: 10, y: 20 };
191    /// let point_ptr = stack.push_copy(&point);
192    /// ```
193    #[inline]
194    pub fn push_copy<T: ?Sized>(&mut self, object: &T) -> NonNull<T>
195    where
196        T: Copy,
197    {
198        let layout = Layout::for_value(object);
199        let ptr = unsafe { self.arena.allocate(layout).unwrap() }.cast();
200        unsafe {
201            ptr.copy_from_nonoverlapping(NonNull::new_unchecked(object as *const T as *mut T), 1);
202        }
203        ptr
204    }
205
206    /// Pushes a value onto the stack with move semantics.
207    ///
208    /// This method takes ownership of the value, moves it onto the stack,
209    /// and returns a pointer to the allocated memory.
210    ///
211    /// # Important Safety Notes
212    ///
213    /// - For types that implement `Drop`, the destructor will not be called automatically
214    ///   when the object is popped or the stack is dropped. You must call `drop_in_place`
215    ///   explicitly when you're done with the object.
216    /// - Modifying the object through the returned pointer (e.g., pushing more bytes onto
217    ///   a `String`) is undefined behavior if it would change the object's size or layout.
218    /// - The memory layout is fixed after allocation and cannot be resized.
219    ///
220    /// # Parameters
221    ///
222    /// * `object` - The value to move onto the stack.
223    ///
224    /// # Returns
225    ///
226    /// A non-null pointer to the allocated memory containing the moved value.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use stack_arena::ObjectStack;
232    ///
233    /// let mut stack = ObjectStack::new();
234    ///
235    /// // Move a String onto the stack
236    /// let string = String::from("hello world");
237    /// let ptr = stack.push(string);
238    /// // string is moved and no longer accessible
239    ///
240    /// // Use the string...
241    /// unsafe {
242    ///     let s = ptr.as_ref();
243    ///     assert_eq!(s, "hello world");
244    /// }
245    ///
246    /// // When done, explicitly drop it
247    /// unsafe {
248    ///     stack.drop_in_place(ptr);
249    /// }
250    ///
251    /// // Move a custom struct onto the stack
252    /// struct Person {
253    ///     name: String,
254    ///     age: u32,
255    /// }
256    ///
257    /// let person = Person {
258    ///     name: String::from("Alice"),
259    ///     age: 30,
260    /// };
261    ///
262    /// let person_ptr = stack.push(person);
263    /// // person is moved and no longer accessible
264    ///
265    /// // Don't forget to drop it when done
266    /// unsafe {
267    ///     stack.drop_in_place(person_ptr);
268    /// }
269    /// ```
270    #[inline]
271    pub fn push<T>(&mut self, object: T) -> NonNull<T>
272    where
273        T: Sized,
274    {
275        let layout = Layout::new::<T>();
276        let ptr = unsafe { self.arena.allocate(layout).unwrap() }.cast();
277        unsafe {
278            ptr.write(object); // Moves object
279        }
280        ptr
281    }
282
283    /// Drops a value in place without deallocating its memory.
284    ///
285    /// This method calls the destructor for a value previously pushed onto the stack
286    /// with `push`. It's useful for types that implement `Drop` and need cleanup.
287    /// The memory itself is not freed until the object is popped or the stack is dropped.
288    ///
289    /// # User Responsibility
290    ///
291    /// It is the user's responsibility to call this method for any values that implement
292    /// `Drop`. Failure to do so may result in resource leaks (e.g., unclosed files,
293    /// unfreed memory from heap allocations inside the object).
294    ///
295    /// # Safety
296    ///
297    /// This method is unsafe because:
298    /// - The pointer must have been obtained from `push<T>` with the same type `T`
299    /// - The value must not have been previously dropped
300    /// - The memory must not be accessed after calling this method
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use stack_arena::ObjectStack;
306    /// use std::fs::File;
307    ///
308    /// let mut stack = ObjectStack::new();
309    ///
310    /// // Push a value that needs dropping
311    /// let string = String::from("hello world");
312    /// let ptr = stack.push(string);
313    ///
314    /// // Use the string...
315    ///
316    /// // When done, drop it in place
317    /// unsafe {
318    ///     stack.drop_in_place(ptr);
319    /// }
320    ///
321    /// // The memory is still allocated but the string is dropped
322    /// ```
323    #[inline]
324    pub unsafe fn drop_in_place<T>(&mut self, ptr: NonNull<T>) {
325        unsafe { ptr.drop_in_place() };
326    }
327
328    /// Removes the most recently pushed object from the stack.
329    ///
330    /// This method follows the LIFO (Last-In-First-Out) principle.
331    /// After popping, any pointers to the popped object become invalid.
332    ///
333    /// # Panics
334    ///
335    /// Panics if the stack is empty or if there is a partial object
336    /// being built (i.e., if `extend` has been called but `finish` has not).
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use stack_arena::ObjectStack;
342    ///
343    /// let mut stack = ObjectStack::new();
344    /// stack.push_bytes(b"hello");
345    /// stack.push_bytes(b"world");
346    /// assert_eq!(stack.len(), 2);
347    ///
348    /// stack.pop();
349    /// assert_eq!(stack.len(), 1);
350    /// ```
351    #[inline]
352    pub fn pop(&mut self) {
353        self.arena.pop();
354        self.partial = false;
355    }
356
357    /// Extends the current object being built with additional data.
358    ///
359    /// This method is used for incrementally building objects. Multiple calls to
360    /// `extend` can be made before finalizing the object with `finish`. This method
361    /// **only supports extending the last allocation** (following LIFO pattern),
362    /// as it uses the underlying arena's grow functionality.
363    ///
364    /// # Parameters
365    ///
366    /// * `value` - The data to append to the current object, which can be any type
367    ///   that can be converted to a byte slice.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use stack_arena::ObjectStack;
373    ///
374    /// let mut stack = ObjectStack::new();
375    /// stack.extend("Hello, ");
376    /// stack.extend("world!");
377    /// let ptr = stack.finish();
378    /// // ptr now points to "Hello, world!"
379    /// ```
380    #[inline]
381    pub fn extend<P: AsRef<[u8]>>(&mut self, value: P) {
382        let data = value.as_ref();
383        if self.partial {
384            let partial_object = self.arena.top().unwrap();
385            let old_layout = Layout::for_value(unsafe { partial_object.as_ref() });
386            let new_layout = unsafe {
387                Layout::from_size_align_unchecked(
388                    old_layout.size() + data.len(),
389                    old_layout.align(),
390                )
391            };
392            let store = unsafe {
393                self.arena
394                    .grow(partial_object.cast(), old_layout, new_layout)
395            }
396            .unwrap();
397            unsafe {
398                store
399                    .cast::<u8>()
400                    .add(old_layout.size())
401                    .copy_from_nonoverlapping(
402                        NonNull::new_unchecked(data.as_ptr().cast_mut()),
403                        data.len(),
404                    );
405            }
406        } else {
407            let store = unsafe { self.arena.allocate(Layout::for_value(data)) }.unwrap();
408            unsafe {
409                store.cast::<u8>().copy_from_nonoverlapping(
410                    NonNull::new_unchecked(data.as_ptr().cast_mut()),
411                    data.len(),
412                )
413            };
414        }
415        self.partial = true;
416    }
417
418    /// Finalizes the current object being built and adds it to the stack.
419    ///
420    /// This method should be called after one or more calls to `extend` to
421    /// finalize the object and make it available on the stack.
422    ///
423    /// # Returns
424    ///
425    /// A non-null pointer to the finalized object.
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use stack_arena::ObjectStack;
431    ///
432    /// let mut stack = ObjectStack::new();
433    /// stack.extend("Hello");
434    /// stack.extend(" world");
435    /// let ptr = stack.finish();
436    /// // ptr now points to "Hello world"
437    /// ```
438    #[inline]
439    pub fn finish(&mut self) -> NonNull<[u8]> {
440        debug_assert!(self.partial);
441        self.partial = false;
442        self.arena.top().unwrap()
443    }
444
445    /// Rolls back to a specific object, freeing it and all objects allocated after it.
446    ///
447    /// This method allows for rolling back to a specific point in the allocation
448    /// history by providing a reference to an object on the stack.
449    ///
450    /// # Parameters
451    ///
452    /// * `data` - A reference to the object to free, along with all objects
453    ///   allocated after it.
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// use stack_arena::ObjectStack;
459    ///
460    /// let mut stack = ObjectStack::new();
461    /// stack.push_bytes(b"first");
462    /// let second = stack.push_bytes(b"second");
463    /// stack.push_bytes(b"third");
464    ///
465    /// // Free "second" and "third"
466    /// stack.rollback(unsafe { second.as_ref() });
467    /// assert_eq!(stack.len(), 1); // Only "first" remains
468    /// ```
469    #[inline]
470    pub fn rollback(&mut self, data: &[u8]) {
471        let data = data.as_ref();
472        unsafe {
473            self.arena.deallocate(
474                NonNull::new_unchecked(data.as_ptr().cast_mut()),
475                Layout::for_value(data),
476            )
477        };
478    }
479}
480
481/// Implementation of the `std::fmt::Write` trait for `ObjectStack`.
482///
483/// This allows using the `write!` macro and other formatting utilities
484/// to write formatted text directly into the object being built.
485///
486/// # Examples
487///
488/// ```
489/// use std::fmt::Write;
490/// use stack_arena::ObjectStack;
491///
492/// let mut stack = ObjectStack::new();
493/// write!(&mut stack, "Hello, {}!", "world").unwrap();
494/// let formatted = stack.finish();
495/// // formatted now points to "Hello, world!"
496/// ```
497impl std::fmt::Write for ObjectStack {
498    /// Writes a string into the arena.
499    ///
500    /// This method extends the current object with the given string.
501    #[inline]
502    fn write_str(&mut self, s: &str) -> std::fmt::Result {
503        self.extend(s);
504        Ok(())
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use super::*;
511    use std::fmt::Write;
512
513    #[test]
514    fn test_lifecycle() {
515        let mut stack = ObjectStack::new();
516        write!(&mut stack, "ab").expect("write");
517        let s = "c";
518        stack.extend(s);
519        let p = unsafe { stack.finish().as_ref() };
520        assert_eq!(p, b"abc");
521    }
522
523    #[test]
524    fn test_push_pop() {
525        let mut stack = ObjectStack::new();
526
527        // Test push_bytes
528        stack.push_bytes(b"hello");
529        assert_eq!(stack.len(), 1);
530        assert!(!stack.is_empty());
531
532        // Test push_bytes multiple items
533        stack.push_bytes(b"world");
534        assert_eq!(stack.len(), 2);
535
536        // Test pop
537        stack.pop();
538        assert_eq!(stack.len(), 1);
539
540        // Test pop to empty
541        stack.pop();
542        assert_eq!(stack.len(), 0);
543        assert!(stack.is_empty());
544    }
545
546    #[test]
547    fn test_extend_small_data() {
548        let mut stack = ObjectStack::new();
549
550        // Extend with data smaller than chunk capacity
551        stack.extend(b"hello");
552
553        // Extend again with small data
554        stack.extend(b" world");
555
556        // Finish and verify
557        let data = unsafe { stack.finish().as_ref() };
558        assert_eq!(data, b"hello world");
559    }
560
561    #[test]
562    fn test_extend_large_data() {
563        let mut stack = ObjectStack::new();
564
565        // Extend with data larger than chunk capacity
566        let large_data = vec![b'x'; 20];
567        stack.extend(&large_data);
568
569        // Finish and verify
570        let data = unsafe { stack.finish().as_ref() };
571        assert_eq!(data, &large_data[..]);
572    }
573
574    #[test]
575    fn test_extend_after_finish() {
576        let mut stack = ObjectStack::new();
577
578        // First object
579        stack.extend("first");
580        let first = unsafe { stack.finish().as_ref() };
581        assert_eq!(first, b"first");
582        // Second object
583        stack.extend(b"second");
584        let second = unsafe { stack.finish().as_ref() };
585
586        assert_eq!(second, b"second");
587
588        // Verify both objects are still valid
589        assert_eq!(first, b"first");
590        assert_eq!(second, b"second");
591    }
592
593    #[test]
594    fn test_free() {
595        let mut stack = ObjectStack::new();
596
597        // Create multiple objects
598        stack.push_bytes(b"first");
599        stack.extend(b"second");
600        let second = unsafe { stack.finish().as_ref() };
601        stack.extend(b"third");
602        let _third = unsafe { stack.finish().as_ref() };
603
604        // Free up to second object
605        stack.rollback(second);
606
607        // Verify stack state
608        assert_eq!(stack.len(), 1); // Only "first" remains
609
610        // Add a new object
611        stack.extend(b"fourth");
612        let fourth = unsafe { stack.finish().as_ref() };
613        assert_eq!(fourth, b"fourth");
614
615        // Verify stack state
616        assert_eq!(stack.len(), 2); // "first" and "fourth"
617    }
618
619    #[test]
620    fn test_write_trait() {
621        let mut stack = ObjectStack::new();
622
623        // Test write_str via the Write trait
624        write!(&mut stack, "Hello, {}!", "world").unwrap();
625        // Finish and verify
626        let data = unsafe { stack.finish().as_ref() };
627        assert_eq!(data, b"Hello, world!");
628    }
629
630    #[test]
631    fn test_empty_data() {
632        let mut stack = ObjectStack::new();
633
634        // Test with empty data
635        stack.extend(b"");
636        let data = unsafe { stack.finish().as_ref() };
637        assert_eq!(data, b"");
638
639        // Test push_bytes with empty data
640        stack.push_bytes(b"");
641        assert_eq!(stack.len(), 2);
642    }
643
644    #[test]
645    fn test_multiple_operations() {
646        let mut stack = ObjectStack::new();
647
648        // Mix of operations
649        stack.push_bytes(b"item1");
650        stack.extend(b"item2-part1");
651        stack.extend(b"-part2");
652        let item2 = unsafe { stack.finish().as_ref() };
653        write!(&mut stack, "item3").unwrap();
654        let item3 = unsafe { stack.finish().as_ref() };
655
656        // Verify
657        assert_eq!(item2, b"item2-part1-part2");
658        assert_eq!(item3, b"item3");
659        assert_eq!(stack.len(), 3);
660
661        // Pop and verify
662        stack.pop();
663        assert_eq!(stack.len(), 2);
664    }
665
666    #[test]
667    fn test_extend_exact_capacity() {
668        let mut stack = ObjectStack::new();
669
670        // Fill exactly to capacity
671        let data = vec![b'x'; 10]; // Same as chunk_size
672        stack.extend(&data);
673
674        // Add more data to trigger new allocation
675        stack.extend(b"more");
676
677        // Finish and verify
678        let result = unsafe { stack.finish().as_ref() };
679        let mut expected = data.clone();
680        expected.extend_from_slice(b"more");
681        assert_eq!(result, expected.as_slice());
682    }
683
684    #[test]
685    fn test_free_all() {
686        let mut stack = ObjectStack::new();
687
688        // Create multiple objects
689        let first = stack.push_bytes(b"first");
690        stack.extend(b"second");
691        let _second = unsafe { stack.finish().as_ref() };
692
693        // Free all objects by freeing the first one
694        stack.rollback(unsafe { first.as_ref() });
695
696        // Verify stack is empty
697        assert_eq!(stack.len(), 0);
698        assert!(stack.is_empty());
699    }
700
701    #[test]
702    #[should_panic]
703    fn test_free_nonexistent() {
704        let mut stack = ObjectStack::new();
705
706        // Create an object
707        stack.push_bytes(b"object");
708        assert_eq!(stack.len(), 1);
709
710        // Try to free a non-existent object
711        let dummy = b"nonexistent";
712        stack.rollback(dummy);
713
714        // Stack should remain unchanged
715        assert_eq!(stack.len(), 1);
716    }
717
718    #[test]
719    fn test_cross_chunk_allocation_deallocation() {
720        // Create an ObjectStack with a custom StackArena that has a very small chunk size
721        let mut stack = ObjectStack {
722            arena: StackArena::with_chunk_size(8),
723            partial: false,
724        };
725
726        let small1 = stack.push_bytes("small1");
727        stack.push("small2");
728        assert_eq!(stack.len(), 2);
729
730        // Pop the second object to maintain LIFO order
731        stack.pop();
732        assert_eq!(stack.len(), 1);
733
734        let large = "start- middle- this-is-a-longer-string-to-trigger-new-chunk";
735        for part in large.split(' ') {
736            stack.extend(part);
737        }
738        assert_eq!(stack.len(), 2);
739
740        // Finish the object
741        let large = stack.finish();
742        assert_eq!(stack.len(), 2);
743        unsafe {
744            assert_eq!(large.as_ref(), large.as_ref());
745        }
746
747        // Verify data integrity
748        assert_eq!(unsafe { small1.as_ref() }, b"small1");
749
750        // Pop in LIFO order
751        stack.pop(); // Pop large
752        assert_eq!(stack.len(), 1);
753
754        stack.pop(); // Pop small1
755        assert!(stack.is_empty());
756
757        // Build a new object with a single extend
758        stack.extend("single-extend-object");
759
760        // Finish the object
761        let single = stack.finish();
762
763        // Verify the object
764        assert_eq!(unsafe { single.as_ref() }, b"single-extend-object");
765
766        // Clean up
767        stack.pop();
768        assert_eq!(stack.len(), 0);
769    }
770
771    #[test]
772    fn test_push_copy() {
773        let mut stack = ObjectStack::new();
774
775        // Test with primitive types
776        let int_val = 42;
777        let int_ptr = stack.push_copy(&int_val);
778        unsafe {
779            let retrieved = *int_ptr.as_ref();
780            assert_eq!(retrieved, 42);
781        }
782
783        let float_val = 3.14;
784        let float_ptr = stack.push_copy(&float_val);
785        unsafe {
786            let retrieved = *float_ptr.as_ref();
787            assert_eq!(retrieved, 3.14);
788        }
789
790        // Test with array
791        let arr = [1, 2, 3, 4, 5];
792        let arr_ptr = stack.push_copy(&arr);
793        unsafe {
794            let retrieved = *arr_ptr.as_ref();
795            assert_eq!(retrieved, [1, 2, 3, 4, 5]);
796        }
797
798        // Test with struct that implements Copy
799        #[derive(Debug, Copy, Clone, PartialEq)]
800        struct Point {
801            x: i32,
802            y: i32,
803        }
804
805        let point = Point { x: 10, y: 20 };
806        let point_ptr = stack.push_copy(&point);
807        unsafe {
808            let retrieved = *point_ptr.as_ref();
809            assert_eq!(retrieved, point);
810        }
811
812        // Verify stack state
813        assert_eq!(stack.len(), 4);
814    }
815
816    #[test]
817    fn test_push_move() {
818        let mut stack = ObjectStack::new();
819
820        // Test with owned String
821        let string = String::from("hello world");
822        let string_len = string.len();
823        let string_ptr = stack.push(string);
824        // string is moved and no longer accessible here
825
826        unsafe {
827            let retrieved = string_ptr.as_ref();
828            assert_eq!(retrieved, "hello world");
829            assert_eq!(retrieved.len(), string_len);
830            stack.drop_in_place(string_ptr);
831        }
832
833        // Test with custom struct
834        struct Person {
835            name: String,
836            age: u32,
837        }
838
839        let person = Person {
840            name: String::from("Alice"),
841            age: 30,
842        };
843
844        let person_ptr = stack.push(person);
845        // person is moved and no longer accessible here
846
847        unsafe {
848            let retrieved: &Person = person_ptr.as_ref();
849            assert_eq!(retrieved.name, "Alice");
850            assert_eq!(retrieved.age, 30);
851            stack.drop_in_place(person_ptr);
852        }
853
854        // Test with Vec
855        let vec = vec![1, 2, 3, 4, 5];
856        let vec_len = vec.len();
857        let vec_ptr = stack.push(vec);
858
859        unsafe {
860            let retrieved: &Vec<i32> = vec_ptr.as_ref();
861            assert_eq!(retrieved.len(), vec_len);
862            assert_eq!(retrieved, &vec![1, 2, 3, 4, 5]);
863            stack.drop_in_place(vec_ptr);
864        }
865
866        // Verify stack state
867        assert_eq!(stack.len(), 3);
868    }
869
870    #[test]
871    fn test_drop_in_place() {
872        use std::sync::Arc;
873        use std::sync::atomic::{AtomicBool, Ordering};
874
875        // Create a type that sets a flag when dropped
876        struct DropDetector {
877            _data: String,
878            drop_flag: Arc<AtomicBool>,
879        }
880
881        impl Drop for DropDetector {
882            fn drop(&mut self) {
883                self.drop_flag.store(true, Ordering::SeqCst);
884            }
885        }
886
887        let mut stack = ObjectStack::new();
888
889        // Create a drop detector with a flag we can check
890        let drop_flag = Arc::new(AtomicBool::new(false));
891        let detector = DropDetector {
892            _data: "test data".to_string(),
893            drop_flag: Arc::clone(&drop_flag),
894        };
895
896        // Push it onto the stack
897        let ptr = stack.push(detector);
898
899        // Verify drop hasn't been called yet
900        assert_eq!(drop_flag.load(Ordering::SeqCst), false);
901
902        // Manually drop it
903        unsafe {
904            stack.drop_in_place(ptr);
905        }
906
907        // Verify drop was called
908        assert_eq!(drop_flag.load(Ordering::SeqCst), true);
909
910        // Memory is still allocated
911        assert_eq!(stack.len(), 1);
912
913        // Pop to free the memory
914        stack.pop();
915        assert_eq!(stack.len(), 0);
916    }
917}