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}