ps-alloc 0.1.0-9

a reasonably safe allocator
Documentation
use crate::{
    header::{layout_of, validate, AllocationHeader},
    DeallocationError,
};

/// This function is the counterpart to [`crate::alloc`].
///
/// Every pointer produced by [`crate::alloc`] or [`crate::realloc`] must eventually be
/// released with this function or [`crate::realloc`]. Unlike C's `free`, passing a null
/// pointer returns an error instead of being a no-op.
///
/// # Errors
///
/// An error is returned if a safety check fails.
///
/// # Safety
///
/// Only pointers produced by [`crate::alloc`] or [`crate::realloc`] may be freed using
/// this function; passing any other pointer causes undefined behaviour. The safety
/// checks are best-effort: any error other than `NullPtr` indicates that undefined
/// behaviour has already occurred, and the checks are performed non-atomically, so
/// concurrent misuse from another thread may go undetected. Unless `ptr` is misaligned
/// or null, **this function will dereference it**.
pub unsafe fn free(ptr: *mut u8) -> Result<(), DeallocationError> {
    if ptr.is_null() {
        return Err(DeallocationError::NullPtr);
    }

    // SAFETY: the caller's obligations for `free` include those of [`validate`].
    let header_ptr = unsafe { validate(ptr) }?;

    // SAFETY: `validate` succeeded, so `header_ptr` satisfies `free_block`'s contract.
    unsafe { free_block(header_ptr) }
}

/// Deallocates the already-validated block whose header is at `header_ptr`.
///
/// # Errors
/// - `Err(LayoutError)` if the stored size does not form a valid layout.
///
/// # Safety
/// `header_ptr` must point to the live, initialized header of a used allocation produced
/// by this crate, as returned by [`validate`].
pub(crate) unsafe fn free_block(
    header_ptr: *mut AllocationHeader,
) -> Result<(), DeallocationError> {
    // SAFETY: `header_ptr` points to a live, initialized header, per this function's
    // contract.
    let layout = layout_of(unsafe { &*header_ptr }.get_size())?;

    // SAFETY: as above; the freed marker is written before `dealloc` so a later free of
    // the same pointer can be detected on a best-effort basis.
    unsafe { &mut *header_ptr }.mark_as_free();

    // SAFETY: `header_ptr` is the base of a live allocation, per this function's
    // contract, and `layout` matches the layout it was allocated with, since it is
    // reconstructed from the total size stored in the header.
    unsafe { std::alloc::dealloc(header_ptr.cast(), layout) }

    Ok(())
}