stack-arena 0.12.0

A fast, stack-like arena allocator for efficient memory management, implemented in Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
use std::{alloc::Layout, ptr::NonNull};

use crate::{Allocator, StackArena};

/// A high-level stack-based object builder that uses `StackArena` for memory management.
///
/// `ObjectStack` provides a more user-friendly interface on top of [`StackArena`](crate::StackArena) for
/// building and managing objects in a stack-like fashion. It supports incremental
/// object construction through the `extend` and `finish` methods, as well as
/// direct formatting through the `std::fmt::Write` trait.
///
/// # Features
///
/// - Push objects onto the stack with move or copy semantics
/// - Push byte slices onto the stack
/// - Build objects incrementally using `extend` and `finish` methods
/// - Implements `std::fmt::Write` for string formatting directly into objects
/// - Stack-like (LIFO) allocation and deallocation pattern
/// - Memory-efficient with minimal overhead
/// - Automatic memory management with chunk reuse
///
/// # Use Cases
///
/// `ObjectStack` is particularly useful for:
///
/// - String building and text processing
/// - Constructing complex objects incrementally
/// - Serialization operations
/// - Any scenario where objects need to be built in stages
///
/// # Examples
///
/// ```
/// use stack_arena::ObjectStack;
/// use std::fmt::Write;
///
/// let mut stack = ObjectStack::new();
///
/// // Push bytes onto the stack
/// stack.push_bytes(b"hello");
///
/// // Push a value with move semantics
/// let string = String::from("world");
/// stack.push(string); // string is moved
///
/// // Push a value with copy semantics
/// let value = 42;
/// stack.push_copy(&value);
///
/// // Build an object incrementally
/// stack.extend("incremental ");
/// write!(&mut stack, "from {}", "Rust").unwrap();
/// let ptr = stack.finish();
///
/// // Access the object
/// let message = unsafe { std::str::from_utf8_unchecked(ptr.as_ref()) };
/// assert_eq!(message, "incremental from Rust");
///
/// // Pop the object when done
/// stack.pop();
/// ```
#[derive(Debug)]
pub struct ObjectStack {
    arena: StackArena,
    partial: bool,
}

impl ObjectStack {
    /// Creates a new empty `ObjectStack` with a default initial capacity.
    ///
    /// The initial chunk size is 1024 bytes, managed by the underlying `StackArena`.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let stack = ObjectStack::new();
    /// assert!(stack.is_empty());
    /// ```
    #[inline]
    pub fn new() -> Self {
        Self {
            arena: StackArena::new(),
            partial: false,
        }
    }

    /// Returns the number of objects currently on the stack.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// assert_eq!(stack.len(), 0);
    ///
    /// stack.push_bytes(b"hello");
    /// assert_eq!(stack.len(), 1);
    /// ```
    #[inline]
    pub fn len(&self) -> usize {
        self.arena.len()
    }

    /// Returns `true` if the stack contains no objects.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// assert!(stack.is_empty());
    ///
    /// stack.push_bytes(b"hello");
    /// assert!(!stack.is_empty());
    /// ```
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.arena.is_empty()
    }

    /// Pushes a byte slice onto the stack.
    ///
    /// This method allocates memory for the byte slice, copies the data,
    /// and returns a pointer to the allocated memory.
    ///
    /// # Parameters
    ///
    /// * `object` - The data to push onto the stack, which can be any type
    ///   that can be converted to a byte slice.
    ///
    /// # Returns
    ///
    /// A non-null pointer to the allocated memory containing the data.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// let ptr = stack.push_bytes(b"hello world");
    /// // The pointer is valid until the object is popped or freed
    /// ```
    #[inline]
    pub fn push_bytes<P: AsRef<[u8]>>(&mut self, object: P) -> NonNull<[u8]> {
        let data = object.as_ref();
        let layout = Layout::for_value(data);
        let ptr = unsafe { self.arena.allocate(layout).unwrap() };
        unsafe {
            ptr.cast().copy_from_nonoverlapping(
                NonNull::new_unchecked(data.as_ptr().cast_mut()),
                data.len(),
            )
        };
        ptr
    }

    /// Pushes a copy of a value onto the stack.
    ///
    /// This method takes a reference to a value that implements the `Copy` trait,
    /// makes a bitwise copy of it, and pushes it onto the stack.
    ///
    /// # Parameters
    ///
    /// * `object` - A reference to the value to copy onto the stack.
    ///
    /// # Returns
    ///
    /// A non-null pointer to the allocated memory containing the copied value.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    ///
    /// // Copy a primitive value
    /// let value = 42;
    /// let ptr = stack.push_copy(&value);
    ///
    /// // Copy a struct that implements Copy
    /// #[derive(Copy, Clone)]
    /// struct Point { x: i32, y: i32 }
    ///
    /// let point = Point { x: 10, y: 20 };
    /// let point_ptr = stack.push_copy(&point);
    /// ```
    #[inline]
    pub fn push_copy<T: ?Sized>(&mut self, object: &T) -> NonNull<T>
    where
        T: Copy,
    {
        let layout = Layout::for_value(object);
        let ptr = unsafe { self.arena.allocate(layout).unwrap() }.cast();
        unsafe {
            ptr.copy_from_nonoverlapping(NonNull::new_unchecked(object as *const T as *mut T), 1);
        }
        ptr
    }

    /// Pushes a value onto the stack with move semantics.
    ///
    /// This method takes ownership of the value, moves it onto the stack,
    /// and returns a pointer to the allocated memory.
    ///
    /// # Important Safety Notes
    ///
    /// - For types that implement `Drop`, the destructor will not be called automatically
    ///   when the object is popped or the stack is dropped. You must call `drop_in_place`
    ///   explicitly when you're done with the object.
    /// - Modifying the object through the returned pointer (e.g., pushing more bytes onto
    ///   a `String`) is undefined behavior if it would change the object's size or layout.
    /// - The memory layout is fixed after allocation and cannot be resized.
    ///
    /// # Parameters
    ///
    /// * `object` - The value to move onto the stack.
    ///
    /// # Returns
    ///
    /// A non-null pointer to the allocated memory containing the moved value.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    ///
    /// // Move a String onto the stack
    /// let string = String::from("hello world");
    /// let ptr = stack.push(string);
    /// // string is moved and no longer accessible
    ///
    /// // Use the string...
    /// unsafe {
    ///     let s = ptr.as_ref();
    ///     assert_eq!(s, "hello world");
    /// }
    ///
    /// // When done, explicitly drop it
    /// unsafe {
    ///     stack.drop_in_place(ptr);
    /// }
    ///
    /// // Move a custom struct onto the stack
    /// struct Person {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// let person = Person {
    ///     name: String::from("Alice"),
    ///     age: 30,
    /// };
    ///
    /// let person_ptr = stack.push(person);
    /// // person is moved and no longer accessible
    ///
    /// // Don't forget to drop it when done
    /// unsafe {
    ///     stack.drop_in_place(person_ptr);
    /// }
    /// ```
    #[inline]
    pub fn push<T>(&mut self, object: T) -> NonNull<T>
    where
        T: Sized,
    {
        let layout = Layout::new::<T>();
        let ptr = unsafe { self.arena.allocate(layout).unwrap() }.cast();
        unsafe {
            ptr.write(object); // Moves object
        }
        ptr
    }

    /// Drops a value in place without deallocating its memory.
    ///
    /// This method calls the destructor for a value previously pushed onto the stack
    /// with `push`. It's useful for types that implement `Drop` and need cleanup.
    /// The memory itself is not freed until the object is popped or the stack is dropped.
    ///
    /// # User Responsibility
    ///
    /// It is the user's responsibility to call this method for any values that implement
    /// `Drop`. Failure to do so may result in resource leaks (e.g., unclosed files,
    /// unfreed memory from heap allocations inside the object).
    ///
    /// # Safety
    ///
    /// This method is unsafe because:
    /// - The pointer must have been obtained from `push<T>` with the same type `T`
    /// - The value must not have been previously dropped
    /// - The memory must not be accessed after calling this method
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    /// use std::fs::File;
    ///
    /// let mut stack = ObjectStack::new();
    ///
    /// // Push a value that needs dropping
    /// let string = String::from("hello world");
    /// let ptr = stack.push(string);
    ///
    /// // Use the string...
    ///
    /// // When done, drop it in place
    /// unsafe {
    ///     stack.drop_in_place(ptr);
    /// }
    ///
    /// // The memory is still allocated but the string is dropped
    /// ```
    #[inline]
    pub unsafe fn drop_in_place<T>(&mut self, ptr: NonNull<T>) {
        unsafe { ptr.drop_in_place() };
    }

    /// Removes the most recently pushed object from the stack.
    ///
    /// This method follows the LIFO (Last-In-First-Out) principle.
    /// After popping, any pointers to the popped object become invalid.
    ///
    /// # Panics
    ///
    /// Panics if the stack is empty or if there is a partial object
    /// being built (i.e., if `extend` has been called but `finish` has not).
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// stack.push_bytes(b"hello");
    /// stack.push_bytes(b"world");
    /// assert_eq!(stack.len(), 2);
    ///
    /// stack.pop();
    /// assert_eq!(stack.len(), 1);
    /// ```
    #[inline]
    pub fn pop(&mut self) {
        self.arena.pop();
        self.partial = false;
    }

    /// Extends the current object being built with additional data.
    ///
    /// This method is used for incrementally building objects. Multiple calls to
    /// `extend` can be made before finalizing the object with `finish`. This method
    /// **only supports extending the last allocation** (following LIFO pattern),
    /// as it uses the underlying arena's grow functionality.
    ///
    /// # Parameters
    ///
    /// * `value` - The data to append to the current object, which can be any type
    ///   that can be converted to a byte slice.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// stack.extend("Hello, ");
    /// stack.extend("world!");
    /// let ptr = stack.finish();
    /// // ptr now points to "Hello, world!"
    /// ```
    #[inline]
    pub fn extend<P: AsRef<[u8]>>(&mut self, value: P) {
        let data = value.as_ref();
        if self.partial {
            let partial_object = self.arena.top().unwrap();
            let old_layout = Layout::for_value(unsafe { partial_object.as_ref() });
            let new_layout = unsafe {
                Layout::from_size_align_unchecked(
                    old_layout.size() + data.len(),
                    old_layout.align(),
                )
            };
            let store = unsafe {
                self.arena
                    .grow(partial_object.cast(), old_layout, new_layout)
            }
            .unwrap();
            unsafe {
                store
                    .cast::<u8>()
                    .add(old_layout.size())
                    .copy_from_nonoverlapping(
                        NonNull::new_unchecked(data.as_ptr().cast_mut()),
                        data.len(),
                    );
            }
        } else {
            let store = unsafe { self.arena.allocate(Layout::for_value(data)) }.unwrap();
            unsafe {
                store.cast::<u8>().copy_from_nonoverlapping(
                    NonNull::new_unchecked(data.as_ptr().cast_mut()),
                    data.len(),
                )
            };
        }
        self.partial = true;
    }

    /// Finalizes the current object being built and adds it to the stack.
    ///
    /// This method should be called after one or more calls to `extend` to
    /// finalize the object and make it available on the stack.
    ///
    /// # Returns
    ///
    /// A non-null pointer to the finalized object.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// stack.extend("Hello");
    /// stack.extend(" world");
    /// let ptr = stack.finish();
    /// // ptr now points to "Hello world"
    /// ```
    #[inline]
    pub fn finish(&mut self) -> NonNull<[u8]> {
        debug_assert!(self.partial);
        self.partial = false;
        self.arena.top().unwrap()
    }

    /// Rolls back to a specific object, freeing it and all objects allocated after it.
    ///
    /// This method allows for rolling back to a specific point in the allocation
    /// history by providing a reference to an object on the stack.
    ///
    /// # Parameters
    ///
    /// * `data` - A reference to the object to free, along with all objects
    ///   allocated after it.
    ///
    /// # Examples
    ///
    /// ```
    /// use stack_arena::ObjectStack;
    ///
    /// let mut stack = ObjectStack::new();
    /// stack.push_bytes(b"first");
    /// let second = stack.push_bytes(b"second");
    /// stack.push_bytes(b"third");
    ///
    /// // Free "second" and "third"
    /// stack.rollback(unsafe { second.as_ref() });
    /// assert_eq!(stack.len(), 1); // Only "first" remains
    /// ```
    #[inline]
    pub fn rollback(&mut self, data: &[u8]) {
        let data = data.as_ref();
        unsafe {
            self.arena.deallocate(
                NonNull::new_unchecked(data.as_ptr().cast_mut()),
                Layout::for_value(data),
            )
        };
    }
}

/// Implementation of the `std::fmt::Write` trait for `ObjectStack`.
///
/// This allows using the `write!` macro and other formatting utilities
/// to write formatted text directly into the object being built.
///
/// # Examples
///
/// ```
/// use std::fmt::Write;
/// use stack_arena::ObjectStack;
///
/// let mut stack = ObjectStack::new();
/// write!(&mut stack, "Hello, {}!", "world").unwrap();
/// let formatted = stack.finish();
/// // formatted now points to "Hello, world!"
/// ```
impl std::fmt::Write for ObjectStack {
    /// Writes a string into the arena.
    ///
    /// This method extends the current object with the given string.
    #[inline]
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
        self.extend(s);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fmt::Write;

    #[test]
    fn test_lifecycle() {
        let mut stack = ObjectStack::new();
        write!(&mut stack, "ab").expect("write");
        let s = "c";
        stack.extend(s);
        let p = unsafe { stack.finish().as_ref() };
        assert_eq!(p, b"abc");
    }

    #[test]
    fn test_push_pop() {
        let mut stack = ObjectStack::new();

        // Test push_bytes
        stack.push_bytes(b"hello");
        assert_eq!(stack.len(), 1);
        assert!(!stack.is_empty());

        // Test push_bytes multiple items
        stack.push_bytes(b"world");
        assert_eq!(stack.len(), 2);

        // Test pop
        stack.pop();
        assert_eq!(stack.len(), 1);

        // Test pop to empty
        stack.pop();
        assert_eq!(stack.len(), 0);
        assert!(stack.is_empty());
    }

    #[test]
    fn test_extend_small_data() {
        let mut stack = ObjectStack::new();

        // Extend with data smaller than chunk capacity
        stack.extend(b"hello");

        // Extend again with small data
        stack.extend(b" world");

        // Finish and verify
        let data = unsafe { stack.finish().as_ref() };
        assert_eq!(data, b"hello world");
    }

    #[test]
    fn test_extend_large_data() {
        let mut stack = ObjectStack::new();

        // Extend with data larger than chunk capacity
        let large_data = vec![b'x'; 20];
        stack.extend(&large_data);

        // Finish and verify
        let data = unsafe { stack.finish().as_ref() };
        assert_eq!(data, &large_data[..]);
    }

    #[test]
    fn test_extend_after_finish() {
        let mut stack = ObjectStack::new();

        // First object
        stack.extend("first");
        let first = unsafe { stack.finish().as_ref() };
        assert_eq!(first, b"first");
        // Second object
        stack.extend(b"second");
        let second = unsafe { stack.finish().as_ref() };

        assert_eq!(second, b"second");

        // Verify both objects are still valid
        assert_eq!(first, b"first");
        assert_eq!(second, b"second");
    }

    #[test]
    fn test_free() {
        let mut stack = ObjectStack::new();

        // Create multiple objects
        stack.push_bytes(b"first");
        stack.extend(b"second");
        let second = unsafe { stack.finish().as_ref() };
        stack.extend(b"third");
        let _third = unsafe { stack.finish().as_ref() };

        // Free up to second object
        stack.rollback(second);

        // Verify stack state
        assert_eq!(stack.len(), 1); // Only "first" remains

        // Add a new object
        stack.extend(b"fourth");
        let fourth = unsafe { stack.finish().as_ref() };
        assert_eq!(fourth, b"fourth");

        // Verify stack state
        assert_eq!(stack.len(), 2); // "first" and "fourth"
    }

    #[test]
    fn test_write_trait() {
        let mut stack = ObjectStack::new();

        // Test write_str via the Write trait
        write!(&mut stack, "Hello, {}!", "world").unwrap();
        // Finish and verify
        let data = unsafe { stack.finish().as_ref() };
        assert_eq!(data, b"Hello, world!");
    }

    #[test]
    fn test_empty_data() {
        let mut stack = ObjectStack::new();

        // Test with empty data
        stack.extend(b"");
        let data = unsafe { stack.finish().as_ref() };
        assert_eq!(data, b"");

        // Test push_bytes with empty data
        stack.push_bytes(b"");
        assert_eq!(stack.len(), 2);
    }

    #[test]
    fn test_multiple_operations() {
        let mut stack = ObjectStack::new();

        // Mix of operations
        stack.push_bytes(b"item1");
        stack.extend(b"item2-part1");
        stack.extend(b"-part2");
        let item2 = unsafe { stack.finish().as_ref() };
        write!(&mut stack, "item3").unwrap();
        let item3 = unsafe { stack.finish().as_ref() };

        // Verify
        assert_eq!(item2, b"item2-part1-part2");
        assert_eq!(item3, b"item3");
        assert_eq!(stack.len(), 3);

        // Pop and verify
        stack.pop();
        assert_eq!(stack.len(), 2);
    }

    #[test]
    fn test_extend_exact_capacity() {
        let mut stack = ObjectStack::new();

        // Fill exactly to capacity
        let data = vec![b'x'; 10]; // Same as chunk_size
        stack.extend(&data);

        // Add more data to trigger new allocation
        stack.extend(b"more");

        // Finish and verify
        let result = unsafe { stack.finish().as_ref() };
        let mut expected = data.clone();
        expected.extend_from_slice(b"more");
        assert_eq!(result, expected.as_slice());
    }

    #[test]
    fn test_free_all() {
        let mut stack = ObjectStack::new();

        // Create multiple objects
        let first = stack.push_bytes(b"first");
        stack.extend(b"second");
        let _second = unsafe { stack.finish().as_ref() };

        // Free all objects by freeing the first one
        stack.rollback(unsafe { first.as_ref() });

        // Verify stack is empty
        assert_eq!(stack.len(), 0);
        assert!(stack.is_empty());
    }

    #[test]
    #[should_panic]
    fn test_free_nonexistent() {
        let mut stack = ObjectStack::new();

        // Create an object
        stack.push_bytes(b"object");
        assert_eq!(stack.len(), 1);

        // Try to free a non-existent object
        let dummy = b"nonexistent";
        stack.rollback(dummy);

        // Stack should remain unchanged
        assert_eq!(stack.len(), 1);
    }

    #[test]
    fn test_cross_chunk_allocation_deallocation() {
        // Create an ObjectStack with a custom StackArena that has a very small chunk size
        let mut stack = ObjectStack {
            arena: StackArena::with_chunk_size(8),
            partial: false,
        };

        let small1 = stack.push_bytes("small1");
        stack.push("small2");
        assert_eq!(stack.len(), 2);

        // Pop the second object to maintain LIFO order
        stack.pop();
        assert_eq!(stack.len(), 1);

        let large = "start- middle- this-is-a-longer-string-to-trigger-new-chunk";
        for part in large.split(' ') {
            stack.extend(part);
        }
        assert_eq!(stack.len(), 2);

        // Finish the object
        let large = stack.finish();
        assert_eq!(stack.len(), 2);
        unsafe {
            assert_eq!(large.as_ref(), large.as_ref());
        }

        // Verify data integrity
        assert_eq!(unsafe { small1.as_ref() }, b"small1");

        // Pop in LIFO order
        stack.pop(); // Pop large
        assert_eq!(stack.len(), 1);

        stack.pop(); // Pop small1
        assert!(stack.is_empty());

        // Build a new object with a single extend
        stack.extend("single-extend-object");

        // Finish the object
        let single = stack.finish();

        // Verify the object
        assert_eq!(unsafe { single.as_ref() }, b"single-extend-object");

        // Clean up
        stack.pop();
        assert_eq!(stack.len(), 0);
    }

    #[test]
    fn test_push_copy() {
        let mut stack = ObjectStack::new();

        // Test with primitive types
        let int_val = 42;
        let int_ptr = stack.push_copy(&int_val);
        unsafe {
            let retrieved = *int_ptr.as_ref();
            assert_eq!(retrieved, 42);
        }

        let float_val = 3.14;
        let float_ptr = stack.push_copy(&float_val);
        unsafe {
            let retrieved = *float_ptr.as_ref();
            assert_eq!(retrieved, 3.14);
        }

        // Test with array
        let arr = [1, 2, 3, 4, 5];
        let arr_ptr = stack.push_copy(&arr);
        unsafe {
            let retrieved = *arr_ptr.as_ref();
            assert_eq!(retrieved, [1, 2, 3, 4, 5]);
        }

        // Test with struct that implements Copy
        #[derive(Debug, Copy, Clone, PartialEq)]
        struct Point {
            x: i32,
            y: i32,
        }

        let point = Point { x: 10, y: 20 };
        let point_ptr = stack.push_copy(&point);
        unsafe {
            let retrieved = *point_ptr.as_ref();
            assert_eq!(retrieved, point);
        }

        // Verify stack state
        assert_eq!(stack.len(), 4);
    }

    #[test]
    fn test_push_move() {
        let mut stack = ObjectStack::new();

        // Test with owned String
        let string = String::from("hello world");
        let string_len = string.len();
        let string_ptr = stack.push(string);
        // string is moved and no longer accessible here

        unsafe {
            let retrieved = string_ptr.as_ref();
            assert_eq!(retrieved, "hello world");
            assert_eq!(retrieved.len(), string_len);
            stack.drop_in_place(string_ptr);
        }

        // Test with custom struct
        struct Person {
            name: String,
            age: u32,
        }

        let person = Person {
            name: String::from("Alice"),
            age: 30,
        };

        let person_ptr = stack.push(person);
        // person is moved and no longer accessible here

        unsafe {
            let retrieved: &Person = person_ptr.as_ref();
            assert_eq!(retrieved.name, "Alice");
            assert_eq!(retrieved.age, 30);
            stack.drop_in_place(person_ptr);
        }

        // Test with Vec
        let vec = vec![1, 2, 3, 4, 5];
        let vec_len = vec.len();
        let vec_ptr = stack.push(vec);

        unsafe {
            let retrieved: &Vec<i32> = vec_ptr.as_ref();
            assert_eq!(retrieved.len(), vec_len);
            assert_eq!(retrieved, &vec![1, 2, 3, 4, 5]);
            stack.drop_in_place(vec_ptr);
        }

        // Verify stack state
        assert_eq!(stack.len(), 3);
    }

    #[test]
    fn test_drop_in_place() {
        use std::sync::Arc;
        use std::sync::atomic::{AtomicBool, Ordering};

        // Create a type that sets a flag when dropped
        struct DropDetector {
            _data: String,
            drop_flag: Arc<AtomicBool>,
        }

        impl Drop for DropDetector {
            fn drop(&mut self) {
                self.drop_flag.store(true, Ordering::SeqCst);
            }
        }

        let mut stack = ObjectStack::new();

        // Create a drop detector with a flag we can check
        let drop_flag = Arc::new(AtomicBool::new(false));
        let detector = DropDetector {
            _data: "test data".to_string(),
            drop_flag: Arc::clone(&drop_flag),
        };

        // Push it onto the stack
        let ptr = stack.push(detector);

        // Verify drop hasn't been called yet
        assert_eq!(drop_flag.load(Ordering::SeqCst), false);

        // Manually drop it
        unsafe {
            stack.drop_in_place(ptr);
        }

        // Verify drop was called
        assert_eq!(drop_flag.load(Ordering::SeqCst), true);

        // Memory is still allocated
        assert_eq!(stack.len(), 1);

        // Pop to free the memory
        stack.pop();
        assert_eq!(stack.len(), 0);
    }
}