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}