oct 0.36.2

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

//! Transmutation functions.

mod test;

use core::mem::ManuallyDrop;
use oct::{
	FromOctets,
	Immutable,
	Init,
	IntoOctets,
	Outlay,
};

/// Transmutes an object to another type.
///
/// The source value is reinterpreted as-is as an
/// object of the destination type. Padding bytes
/// are not preserved, but the initialisation states
/// of [`MaybeUninit`] fields are.
///
/// [`MaybeUninit`]: core::mem::MaybeUninit
///
/// # Panics
///
/// This function will panic at translation time if
/// `T` and `U` aren't of the same size.
#[inline]
#[must_use]
#[track_caller]
pub const fn transmute<T, U>(value: T) -> U
where
	T: IntoOctets + Init,
	U: FromOctets,
{
	// Bound notes:
	// * `T: Init` guarantees that the returned object
	//    doesn't assume uninitialised data being ini-
	//    tialised.

	// Test that the two types are of the same size.

	const {
		assert!(
			size_of::<U>() == size_of::<T>(),
			"cannot transmute types of different sizes",
		);
	}

	// Reinterpret the object.

	// SAFETY: We have asserted that both types are of
	// equal size. `U` bounds guarantee lack of type
	// invariants.
	unsafe { transmute_unchecked(value) }
}

/// Transmutes a reference to another type.
///
/// See also [`transmute_ref_with_metadata`].
///
/// # Panics
///
/// This function will panic if any of the following
/// prerequisites are broken:
///
/// * The size of the object referenced by `r` is
///   not a valid size for `U` objects, or metadata
///   cannot indisputely be selected for the size.
/// * The object referenced by `r` is not aligned to
///   the requirements of `U`.
///
/// # Examples
///
/// Transmuting an object to a slice:
///
/// ```rust
/// use oct::transmute_ref;
///
/// let src = i64::MAX.to_le();
///
/// let dst: &[u32] = transmute_ref(&src);
///
/// assert_eq!(
///     dst,
///     &[
///         0xFFFF_FFFFu32.to_le(),
///         0x7FFF_FFFFu32.to_le(),
///     ],
/// );
/// ```
#[inline]
#[must_use]
#[track_caller]
pub fn transmute_ref<T, U>(r: &T) -> &U
where
	T: ?Sized + IntoOctets + Init + Immutable,
	U: ?Sized + FromOctets + Outlay + Immutable,
{
	let size = size_of_val(r);

	let metadata = U::classify_size(size)
		.expect("cannot transmute reference with incompatible object size");

	transmute_ref_with_metadata(r, metadata)
}

/// Transmutes a mutable reference to another type,
/// using predefined metadata.
///
/// # Panics
///
/// This function will panic if the provied object
/// is not sufficiently large to read a `U` object
/// with the specified metadata.
#[inline]
#[must_use]
#[track_caller]
pub fn transmute_ref_with_metadata<T, U>(r: &T, metadata: U::Metadata) -> &U
where
	T: ?Sized + IntoOctets + Init + Immutable,
	U: ?Sized + FromOctets + Outlay + Immutable,
{
	// Compute the required size.

	let size = if let Ok(layout) = U::layout_for_metadata(metadata) {
		layout.size()
	} else {
		panic!("cannot construct layout for dst with length `{metadata:?}`");
	};

	// Test that the buffer is sufficiently large.

	assert!(
		size_of_val(r) >= size,
		"cannot construct reference from undersized octets",
	);

	let data = (&raw const *r).cast::<u8>();

	let p = U::ptr_from_raw_parts(data, metadata);

	assert_eq!(
		p.addr() % U::ALIGN,
		0,
		"cannot construct reference from unaligned octets",
	);

	// SAFETY:
	// * We have tested that it is aligned to the re-
	//   quirements of `Self`.
	//
	// * It derives from a reference and can therefore
	//   not be null.
	//
	// * We have tested that the size of the object is
	//   contained within the provided octets.
	//
	// * The returned reference inherits the lifetime
	//   of the provided octets.
	//
	// * The `T: Init` bound requires that the destina-
	//   tion of `r` is already in an initialised
	//   state, and the `Self: FromOctets` bound re-
	//   quires that any such state is valid for
	//   `Self`.
	//
	// Furthermore, writes cannot be made to the desti-
	// nation thanks to the `Self: Immutable` bound.
	unsafe { &*p }
}

/// Transmutes a mutable reference to another type.
///
/// # Panics
///
/// This function will panic if the size of the
/// provided object is not a valid size for `U`
/// objects, or if the object is unaligned to `U`.
#[inline]
#[must_use]
#[track_caller]
pub fn transmute_mut<T, U>(r: &mut T) -> &mut U
where
	T: ?Sized + IntoOctets + FromOctets + Init,
	U: ?Sized + FromOctets + Outlay + Init,
{
	let size = size_of_val(r);

	let metadata = U::classify_size(size)
		.expect("cannot transmute reference with incompatible object size");

	transmute_mut_with_metadata(r, metadata)
}

/// Transmutes a mutable reference to another type,
/// using predefined metadata.
///
/// # Panics
///
/// This function will panic if the provied object
/// is not sufficiently large to read a `U` object
/// with the specified metadata.
#[inline]
#[must_use]
#[track_caller]
pub fn transmute_mut_with_metadata<T, U>(r: &mut T, metadata: U::Metadata) -> &mut U
where
	T: ?Sized + IntoOctets + FromOctets + Init,
	U: ?Sized + FromOctets + Outlay + Init,
{
	// Compute the required size.

	let size = if let Ok(layout) = U::layout_for_metadata(metadata) {
		layout.size()
	} else {
		panic!("cannot construct layout for dst with length `{metadata:?}`");
	};

	// Test that the buffer is sufficiently large.

	assert!(
		size_of_val(r) >= size,
		"cannot transmute reference from undersized object",
	);

	let data = (&raw mut *r).cast::<u8>();

	let p = U::ptr_from_raw_parts_mut(data, metadata);

	assert_eq!(
		p.addr() % U::ALIGN,
		0,
		"cannot transmute reference with decreasing alignement",
	);

	// SAFETY:
	// * We have tested that it is aligned to the re-
	//   quirements of `Self`.
	//
	// * It derives from a reference and can therefore
	//   not be null.
	//
	// * We have tested that the size of the object is
	//   contained within the provided octets.
	//
	// * The reference inherits the exclusivity of the
	//   provided octet slice.
	//
	// * The returned reference inherits the lifetime
	//   of the provided octets.
	//
	// * The `T: Init` bound requires that the destina-
	//   tion of `r` is already in an initialised
	//   state, and the `Self: FromOctets` bound re-
	//   quires that any such state is valid for
	//   `Self`.
	//
	// Furthermore, writes cannot be made to the desti-
	// nation thanks to the `Self: Immutable` bound.
	unsafe { &mut *p }
}

/// Unsafely transmutes an object to another type.
///
/// The source value is reinterpreted as-is as an
/// object of the destination type. Padding bytes
/// are not preserved, but the initialisation states
/// of [`MaybeUninit`] fields are.
///
/// [`MaybeUninit`]: core::mem::MaybeUninit
///
/// This function is massively unsafe but also
/// allows for much more (sound) usage than
/// [`transmute`] and [`core::mem::transmute`].
///
/// # Safety
///
/// The following guarantees must be upheld when
/// transmuting objects:
///
/// * `U` and `T` must be of the exact same size.
/// * Any uninitialised octet in `value` must also
///   be permitted as uninitialised by `U`.
/// * Any initialised octet in `value` must have
///   value that is also permitted by `U`.
/// * `value` -- if a pointer -- was initially
///   transm from an integer.
///
/// A violation of any of these requirements results
/// in undefined behaviour. See also the
/// documentation for [`core::mem::transmute`].
#[inline(always)]
#[must_use]
#[track_caller]
pub const unsafe fn transmute_unchecked<T, U>(value: T) -> U {
	debug_assert!(
		size_of::<U>() == size_of::<T>(),
		"cannot transmute types of different sizes",
	);

	#[repr(C)]
	union Transmute<Src, Dst> {
		src: ManuallyDrop<Src>,
		dst: ManuallyDrop<Dst>,
	}

	// Wrap the object in the union.

	let transmute = Transmute { src: ManuallyDrop::new(value) };

	// Reread the object as the destination type.

	// SAFETY: Caller guarantees correct representa-
	// tion.
	unsafe { ManuallyDrop::into_inner(transmute.dst) }
}