oct 0.34.0

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

//! Transmutation functions.

mod test;

use crate::{
	FromOctets,
	Immutable,
	Init,
	IntoOctets,
	Outlay,
};

use core::mem::ManuallyDrop;

/// Transmutes an object to another type.
///
/// The raw octet representation of the object is
/// reused. Note, however, that this isn't necessar-
/// ily equivalent to the new value being equal to
/// the old one (as per [`PartialEq::eq`]).
///
/// # 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 constant reference to another
/// reference 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`.
///
/// # 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(),
///     ],
/// );
/// ```
#[must_use]
#[track_caller]
pub fn transmute_ref<T, U>(r: &T) -> &U
where
	T: ?Sized + IntoOctets + Init + Immutable,
	U: ?Sized + FromOctets + Outlay + Immutable,
{
	// Bound notes:
	// * `T: Init` guarantees that the returned refer-
	//    ence doesn't assume uninitialised data being ini-
	//    tialised.
	//
	// * `T: Immutable` guarantees that the destination
	//   of the returned reference doesn't mutate.
	//
	// * `U`: `Outlay` allows us to determine appropri-
	//   ate metadata.
	//
	// * `U: Immutable` guarantees that the destination
	//   of the provided reference doesn't mutate.
	//
	// Note that `U: Init` is unnecessary as the re-
	// turned reference cannot deinitialise the refer-
	// enced object.

	// Retrieve the size of the underlying object.

	let size = size_of_val(r);

	// Test that the size is unambiguous for the desti-
	// nation type.

	let meta = U::classify_size(size)
		.expect("cannot transmute objects of different sizes");

	// Retrieve a pointer to the underlying object.

	let data = r.as_octets().as_ptr();

	// Test that the underlying object is aligned for
	// the destination type.

	assert!(
		data.addr().is_multiple_of(U::ALIGN),
		"cannot transmute reference to increasing alignement",
	);

	// Reinterpret the object as the destination type.

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

	// Reborrow the underlying object as the destina-
	// tion type.

	// SAFETY: We have asserted that both types are of
	// equal size and that alignement isn't a concern.
	// `U` bounds guarantee lack of type invariants.
	unsafe { &*p }
}

/// Transmutes a mutable reference to another
/// reference 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`.
#[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,
{
	// Bound notes:
	// * `T: FromOctets` allows for arbitrary writes to
	//   the returned reference.
	//
	// * `T: Init` guarantees that the returned refer-
	//    ence doesn't assume uninitialised data being ini-
	//    tialised.
	//
	// * `U`: `Outlay` allows us to determine appropri-
	//   ate metadata.
	//
	// * `U: Init` guarantees that the original object
	//   can't be deinitialised via the returned refer-
	//   ence.

	// Retrieve the size of the underlying object.

	let size = size_of_val(r);

	// Test that the size is unambiguous for the desti-
	// nation type.

	let meta = U::classify_size(size)
		.expect("cannot transmute objects of different sizes");

	// Retrieve a pointer to the underlying object.

	let data = r.as_octets_mut().as_mut_ptr();

	// Test that the underlying object is aligned for
	// the destination type.

	assert!(
		data.addr().is_multiple_of(U::ALIGN),
		"cannot transmute reference to increasing alignement",
	);

	// Reinterpret the object as the destination type.

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

	// Reborrow the underlying object as the destina-
	// tion type.

	// SAFETY: We have asserted that both types are of
	// equal size and that alignement isn't a concern.
	// `U` bounds guarantee lack of type invariants.
	unsafe { &mut *p }
}

/// Unsafely transmutes an object to another type.
///
/// The raw octet representation of the object is
/// reused. Note, however, that this isn't necessar-
/// ily equivalent to the new value being equal to
/// the old one (as per [`PartialEq::eq`]).
///
/// This function should be used rarely, with
/// [`transmute`] guaranteeing safe transmutations
/// instead. This function may be used instead of
/// [`core::mem::transmute`] when transmuting
/// dependently-sized types (e.g. [arrays]).
///
/// [arrays]: (array)
///
/// # Safety
///
/// The sizes of `T` and `U` must be exactly equal.
/// Furthermore, callers guarantee that the exact
/// representation used by the provided value is
/// valid for objects of the destination type.
#[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) }
}