oct 0.36.2

Octonary transcodings.
Documentation
// Copyright 2024-2026 Gabriel Bjørnager Jensen.
//
// SPDX: MIT OR Apache-2.0

//! The [`Outlay`] trait.

mod test;

use core::alloc::{Layout, LayoutError};
use core::fmt::Debug;
use core::hash::Hash;
use core::num::NonZero;
use core::panic::UnwindSafe;
use core::ptr::{self, dangling, dangling_mut, NonNull};
use oct::Immutable;

#[cfg(feature = "bstr")]
use core::bstr::ByteStr;

/// Denotes an outlayable type.
///
/// Note that this trait need to be implemented
/// manually for <code>![Sized]</code> types.
///
/// # Safety
///
/// See specific, associated items.
pub unsafe trait Outlay {
	/// The metadata type used by pointers to this type.
	///
	/// This type must be identical to
	/// [`Pointee::Metadata`].
	///
	/// [`Pointee::Metadata`]: core::ptr::Pointee::Metadata
	type Metadata:
		Debug
		+ Copy
		+ Hash
		+ Immutable
		+ Ord
		+ Send
		+ Sync
		+ Unpin
		+ UnwindSafe;

	/// The alignement requirement of this type.
	///
	/// This value must be a power of two and may never
	/// be zero.
	const ALIGN: usize;

	/// Classifies the size of an octet representation.
	///
	/// This function determines metadata that could be
	/// used pointing to a `Self` object of the provided
	/// size.
	///
	/// If no such metadata exists, or is disputable
	/// (e.g. if multiple metadata would yield the same
	/// object size,) this function must return
	/// [`None`]. As a rule of thumb:
	///
	/// * Sized types accept any sufficiently-large
	///   buffer size.
	/// * DSTs should only accept *exact* buffer sizes,
	///   except when multiple metadata yield the same,
	///   final object size.
	///
	/// The size of an object with the returned
	/// metadata may be different from this here
	/// provided size but never larger.
	#[must_use]
	fn classify_size(size: usize) -> Option<Self::Metadata>;

	/// Constructs metadata for an allocation with
	/// specific metadata.
	///
	/// The returned layout can be used to describe a
	/// `Self` allocation, based on the provided
	/// metadata. The size denoted by the layout may be
	/// zero.
	///
	/// # Errors
	///
	/// This function must return an [`Err`] instance if
	/// the size of the resulting layout is
	/// unrepresentable by [`isize`].
	fn layout_for_metadata(metadata: Self::Metadata) -> Result<Layout, LayoutError>;

	/// Constructs a raw, constant pointer from raw
	/// metadata.
	///
	/// The returned pointer inherits the provenance of
	/// the provided one. Standard rules apply for using
	/// the returned pointer.
	#[must_use]
	fn ptr_from_raw_parts(data: *const u8, metadata: Self::Metadata) -> *const Self;

	/// Constructs a raw, mutable pointer from raw
	/// parts.
	///
	/// The returned pointer inherits the provenance of
	/// the provided one. Standard rules apply for using
	/// the returned pointer.
	#[must_use]
	fn ptr_from_raw_parts_mut(data: *mut u8, metadata: Self::Metadata) -> *mut Self;

	/// Constructs a new, dangling, constant pointer
	/// with specific metadata.
	///
	/// The returned pointer is guaranteed to be
	/// aligned and non-null.
	#[must_use]
	fn ptr_dangling_with_metadata(metadata: Self::Metadata) -> *const Self;

	/// Constructs a new, dangling, mutable pointer with
	/// specific metadata.
	///
	/// The returned pointer is guaranteed to be
	/// aligned and non-null.
	#[must_use]
	fn ptr_dangling_with_metadata_mut(metadata: Self::Metadata) -> *mut Self;

	/// Constructs a new, dangling, non-null pointer
	/// with specific metadata.
	#[inline(always)]
	#[must_use]
	#[track_caller]
	fn non_null_dangling_with_metadata(metadata: Self::Metadata) -> NonNull<Self> {
		let p = Self::ptr_dangling_with_metadata_mut(metadata);

		debug_assert_eq!(
			p.addr() % Self::ALIGN,
			0,
			"`ptr_dangling_with_metadata_mut` returned a non-aligned pointer",
		);

		debug_assert!(
			p.is_null(),
			"`ptr_dangling_with_metadata_mut` returned a null pointer",
		);

		// SAFETY: `ptr_dangling_with_metadata_mut` return-
		// ing a non-null pointer is a safety requirement
		// for `Outlay`.
		unsafe { NonNull::new_unchecked(p) }
	}
}

unsafe impl<T> Outlay for T {
	type Metadata = ();

	// NOTE: No type may be zero-aligned.
	const ALIGN: usize = align_of::<Self>();

	#[inline]
	fn classify_size(size: usize) -> Option<Self::Metadata> {
		// There is only one, possible metadatum, so we
		// just test that the size is sufficient.
		//
		// NOTE: This implementation still accepts sizes
		// larger than `isize::MAX` as those will still be
		// rounded down when the metadata is applied.
		if size >= size_of::<Self>() {
			Some(())
		} else {
			None
		}
	}

	#[inline]
	fn layout_for_metadata((): Self::Metadata) -> Result<Layout, LayoutError> {
		Ok(Layout::new::<T>())
	}

	#[inline(always)]
	fn ptr_from_raw_parts(data: *const u8, _meta: Self::Metadata) -> *const Self {
		data.cast::<Self>()
	}

	#[inline(always)]
	fn ptr_from_raw_parts_mut(data: *mut u8, _meta: Self::Metadata) -> *mut Self {
		data.cast::<Self>()
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata((): Self::Metadata) -> *const Self {
		dangling::<Self>()
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata_mut((): Self::Metadata) -> *mut Self {
		dangling_mut::<Self>()
	}
}

#[cfg(feature = "bstr")]
unsafe impl Outlay for ByteStr {
	type Metadata = <[u8] as Outlay>::Metadata;

	const ALIGN: usize = <[u8] as Outlay>::ALIGN;

	#[inline(always)]
	fn classify_size(size: usize) -> Option<Self::Metadata> {
		// NOTE: Same rules as for `<[u8]>`.
		<[u8]>::classify_size(size)
	}

	#[inline(always)]
	fn layout_for_metadata(metadata: Self::Metadata) -> Result<Layout, LayoutError> {
		<[u8]>::layout_for_metadata(metadata)
	}

	#[inline(always)]
	fn ptr_from_raw_parts(data: *const u8, metadata: Self::Metadata) -> *const Self {
		// NOTE: `<*const T>::cast` requires `T: Sized`.
		<[u8]>::ptr_from_raw_parts(data, metadata) as *const Self
	}

	#[inline(always)]
	fn ptr_from_raw_parts_mut(data: *mut u8, metadata: Self::Metadata) -> *mut Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_from_raw_parts_mut(data, metadata) as *mut Self
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata(metadata: Self::Metadata) -> *const Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_dangling_with_metadata(metadata) as *const Self
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata_mut(metadata: Self::Metadata) -> *mut Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_dangling_with_metadata_mut(metadata) as *mut Self
	}
}

// SORT: slice
unsafe impl<T> Outlay for [T] {
	type Metadata = usize;

	// SAFETY: No type may be zero-aligned.
	const ALIGN: usize = align_of::<T>();

	#[inline]
	fn classify_size(size: usize) -> Option<Self::Metadata> {
		// The size function for ZST slices is not symmet-
		// ric, so we can't pick a definitive length.
		let element_size = NonZero::new(size_of::<T>())?;

		// No object can have a size larger than
		// `isize::MAX`, but we don't enforce that here.

		if size % element_size == 0 {
			Some(size / element_size)
		} else {
			None
		}
	}

	#[inline]
	fn layout_for_metadata(metadata: Self::Metadata) -> Result<Layout, LayoutError> {
		Layout::array::<T>(metadata)
	}

	#[inline]
	fn ptr_from_raw_parts(data: *const u8, metadata: Self::Metadata) -> *const Self {
		let data = data.cast::<T>();
		ptr::slice_from_raw_parts(data, metadata)
	}

	#[inline]
	fn ptr_from_raw_parts_mut(data: *mut u8, metadata: Self::Metadata) -> *mut Self {
		let data = data.cast::<T>();
		ptr::slice_from_raw_parts_mut(data, metadata)
	}

	#[inline]
	fn ptr_dangling_with_metadata(metadata: Self::Metadata) -> *const Self {
		let data = dangling::<T>();
		ptr::slice_from_raw_parts(data, metadata)
	}

	#[inline]
	fn ptr_dangling_with_metadata_mut(metadata: Self::Metadata) -> *mut Self {
		let data = dangling_mut::<T>();
		ptr::slice_from_raw_parts_mut(data, metadata)
	}
}

unsafe impl Outlay for str {
	type Metadata = <[u8] as Outlay>::Metadata;

	const ALIGN: usize = <[u8] as Outlay>::ALIGN;

	#[inline(always)]
	fn classify_size(size: usize) -> Option<Self::Metadata> {
		// NOTE: Same rules as for `<[u8]>`.
		<[u8]>::classify_size(size)
	}

	#[inline(always)]
	fn layout_for_metadata(metadata: Self::Metadata) -> Result<Layout, LayoutError> {
		<[u8]>::layout_for_metadata(metadata)
	}

	#[inline(always)]
	fn ptr_from_raw_parts(data: *const u8, metadata: Self::Metadata) -> *const Self {
		// NOTE: `<*const T>::cast` requires `T: Sized`.
		<[u8]>::ptr_from_raw_parts(data, metadata) as *const Self
	}

	#[inline(always)]
	fn ptr_from_raw_parts_mut(data: *mut u8, metadata: Self::Metadata) -> *mut Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_from_raw_parts_mut(data, metadata) as *mut Self
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata(metadata: Self::Metadata) -> *const Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_dangling_with_metadata(metadata) as *const Self
	}

	#[inline(always)]
	fn ptr_dangling_with_metadata_mut(metadata: Self::Metadata) -> *mut Self {
		// NOTE: `<*mut T>::cast` requires `T: Sized`.
		<[u8]>::ptr_dangling_with_metadata_mut(metadata) as *mut Self
	}
}