Appendix: FAQ and Glossary

Reinventing From Scratch — Box<T>

Chapter 12 — Appendix: Full Minimal Impl, FAQ & Glossary

12.1 Full minimal implementation (sized T)

use std::alloc::{alloc, dealloc, Layout};
use std::ops::{Deref, DerefMut};
use std::ptr;

pub struct MyBox<T> { ptr: *mut T }

impl<T> MyBox<T> {
    pub fn new(value: T) -> Self {
        unsafe {
            let p = alloc(Layout::new::<T>()) as *mut T;
            if p.is_null() { panic!("allocation failed"); }
            ptr::write(p, value);
            MyBox { ptr: p }
        }
    }

    #[inline] pub fn as_ptr(&self) -> *const T { self.ptr }
    #[inline] pub fn as_mut_ptr(&mut self) -> *mut T { self.ptr }

    pub fn into_raw(mut this: Self) -> *mut T {
        let p = this.ptr;
        this.ptr = std::ptr::null_mut();
        std::mem::forget(this);
        p
    }

    /// # Safety
    /// `ptr` must originate from `MyBox::into_raw` with the same `T` and allocator.
    pub unsafe fn from_raw(ptr: *mut T) -> Self { MyBox { ptr } }

    pub fn leak(this: Self) -> &'static mut T {
        let p = Self::into_raw(this);
        unsafe { &mut *p }
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target { unsafe { &*self.ptr } }
}
impl<T> DerefMut for MyBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.ptr } }
}

impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        unsafe {
            if self.ptr.is_null() { return; }
            ptr::drop_in_place(self.ptr);
            dealloc(self.ptr.cast(), Layout::new::<T>());
        }
    }
}

12.2 FAQ

  • Does moving Box<T> move T? No. Only the pointer moves; T stays at the same heap address.
  • When to use Box<T> vs Rc<T>/Arc<T>? Use Box for unique ownership; Rc/Arc for shared.
  • Do I need Pin<Box<T>> for a stable address? Box already keeps a stable address; Pin also forbids moves through the type system and API.
  • Can I write my own DST box? Possible but tricky: you must handle fat pointers and reconstruct Layout using metadata.

12.3 Glossary

  • UB (Undefined Behavior): Anything may happen; compiler assumptions are violated.
  • Layout: Size + alignment descriptor for allocation.
  • ZST: Zero-Sized Type (e.g., ()); still has identity.
  • NonNull: Non-null raw pointer wrapper; enables niche optimizations.
  • Fat pointer: Pointer + metadata (length or vtable).

Final Exercise

Port this MyBox<T> into a small crate with tests and run under Miri. Add an example that leaks a global configuration and explain why the leak is acceptable.


Deep Dive: Ownership Proofs, Drop Order, and DST Considerations

A. Formal Invariants for MyBox<T> (Sized)

  • B1 (Pointer Validity): ptr is either null only after into_raw or a valid, properly aligned pointer to initialized T.
  • B2 (Single Drop): The destructor of T is invoked exactly once if and only if ptr is non-null at Drop time.
  • B3 (Dealloc after Drop): dealloc(layout_of::<T>()) is called exactly once, and only after drop_in_place.
  • B4 (From/Into Raw Consistency): from_raw only accepts pointers produced by into_raw of the same type/allocator; mixing allocators is UB.
  • B5 (No References to Uninit): No &/&mut references are created before ptr::write initializes the allocation.

B. Proof Sketches

B.1 Single DropDrop checks for null and calls drop_in_place once; into_raw nulls out ptr and forgets self, preventing Drop from running on a live value.
B.2 No Use-After-Free — Deallocation happens only after the destructor; references returned by Deref are derived from a live ptr and never stored beyond the box’s lifetime.
B.3 Panic Safety — If constructor panics before publishing, no ownership is established; if Drop panics (should not), process aborts, avoiding double-unwind corruption.

C. DST Box Notes

  • Slices (Box<[T]>): store length; the fat pointer (data, len) enables correct deallocation.
  • Box<str>: same as [u8] with UTF‑8 invariant; length in metadata.
  • Box<dyn Trait>: fat pointer (data, vtable); the vtable encodes drop and size/alignment; std uses compiler magic for correct layout.

D. Interop Patterns

  • FFI Ownership Transfer: into_raw -> C takes ownership; C must call back into Rust with from_raw or a custom free.
  • Leaking Globals: leak returns 'static reference, acceptable for process lifetime singletons; document intent.

E. Debugging

  • Double Drop: look for *p assignment instead of ptr::write on uninitialized memory.
  • Mismatched Layout: using dealloc with wrong Layout causes heap corruption; keep Layout::new::<T>() paired.

F. Exercises

  1. Implement try_new returning Result<MyBox<T>, AllocError>.
  2. Add into_inner(self) -> T by ptr::read and skipping dealloc? Explain why you must still dealloc after moving T.
  3. Implement MyVec::into_boxed_slice that hands RawVec buffer to a Box<[T]> safely.

FAQ (Extended)

Q: Does Box<T> guarantee a stable address? A: Yes, the pointee’s address is stable for the life of the box; moving the box moves only the handle.
Q: Why ptr::write not *p = value? A: The latter reads/drops the previous contents (uninitialized), which is UB.
Q: Can Box<T> be null? A: By design, standard Box<T> is non-null; our MyBox may set ptr = null only as a consumed sentinel post-into_raw.
Q: Is Pin<Box<T>> needed for stable address? A: Not for stability; Pin is for forbidding moves via the API.