oct 0.26.0

Octonary transcodings.
Documentation
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.

//! Raw fields for [`io::Error`].
//!
//! [`io::Error`]: crate::io::Error

use crate::io::{ErrorData, ErrorKind, SimpleError};

#[cfg(feature = "alloc")]
use crate::io::CustomError;

use core::fmt::{self, Debug, Formatter};
use core::num::NonZero;
use core::ptr::NonNull;

#[cfg(feature = "alloc")]
use alloc::boxed::Box;

// NOTE: We like niche optimisations.
// SAFETY: We need to use some pointer type to keep
// track of provenance.
/// The raw, packed error data.
/// A packed input/output error.
#[derive(Clone, Copy)]
pub(super) struct Raw(NonNull<()>);

impl Raw {
	/// Constructs a new [`Simple`] error.
	///
	/// [`Simple`]: ErrorData::Simple
	pub const fn new_simple(message: &'static SimpleError) -> Self {
 		// SAFETY: The alignement of `SimpleError` ensures
		// some always-zero padding bits, which equate to
		// `Tag::Simple`. This allows for us to avoid
		// manipulating the raw address, thus enabling this
		// operation in `const`. Lastly, the pointer
		// derives, which guarantees that it isn't null.
		let p = NonNull::from_ref(message).cast();
		Self(p)
	}

	/// Constructs a new [`Inline`] error.
	///
	/// [`Inline`]: ErrorData::Inline
	pub fn new_inline(kind: ErrorKind) -> Self {
		let mut addr = 0usize;
		addr |= Tag::Inline as usize;
		addr |= (kind as usize) << Tag::BITS;

		// SAFETY: `Tag::Inline` gives one bit.
		let addr = unsafe { NonZero::new_unchecked(addr) };

		// NOTE: We don't need any provenance as the
		// address will not be dereferenced.
		let p = NonNull::without_provenance(addr);
		Self(p)
	}

	/// Constructs a new [`Custom`] error.
	///
	/// [`Custom`]: ErrorData::Custom
	#[cfg(feature = "alloc")]
	pub fn new_custom(custom: Box<CustomError>) -> Self {
		let mut p = Box::into_raw(custom) as *mut ();

		p = unsafe { p.byte_add(Tag::Custom as usize) };

		// SAFETY: `Tag::Custom` gives one bit, although
		// the original address couldn't've'd been null to
		// begin with.
		let p = unsafe { NonNull::new_unchecked(p) };
		Self(p)
	}

	/// Constructs a new [`Os`] error.
	///
	/// [`Os`]: ErrorData::Os
	#[cfg(feature = "std")]
	#[must_use]
	pub fn new_os(raw: i32) -> Self {
		let mut addr = 0usize;
		addr |= Tag::Os as usize;
		addr |= (raw as usize) << Tag::BITS;

		// SAFETY: `Tag::Os` gives one bit.
		let addr = unsafe { NonZero::new_unchecked(addr) };

		// NOTE: We don't need any provenance as the
		// address will not be dereferenced.
		let p = NonNull::without_provenance(addr);
		Self(p)
	}

	// NOTE: This function must be an associated non-
	// method as we otherwise couldn't convert the
	// pointer to mutable reference.
	/// Decodes fields from raw data.
	#[must_use]
	pub fn data(self) -> ErrorData {
		match self.tag() {
			Tag::Simple => {
				// NOTE: `Tag::Simple` is zero and does not need
				// to be removed.
				let p = self.0.as_ptr().cast_const() as *const SimpleError;

				// SAFETY: Caller guarantees this.
				let message = unsafe { &*p };

				ErrorData::Simple(message)
			}

			Tag::Inline => {
				let kind = self.as_usize() >> Tag::BITS;

				// SAFETY: Caller guarantees this.
				let kind = unsafe { ErrorKind::from_usize_unchecked(kind) };

				ErrorData::Inline(kind)
			}

			#[cfg(feature = "alloc")]
			Tag::Custom => {
				let mut p = self.0.as_ptr() as *mut CustomError;

				// SAFETY: The result can, at least, be null.
				p = unsafe { p.byte_sub(Tag::Custom as usize) };

				ErrorData::Custom(p)
			}

			#[cfg(feature = "std")]
			Tag::Os => {
				let raw = (self.as_usize() >> Tag::BITS) as i32;
				ErrorData::Os(raw)
			}
		}
	}

	/// Retrieves the data tag.
	#[inline]
	#[must_use]
	fn tag(self) -> Tag {
		const TAG_MASK: usize = !(!0 << Tag::BITS);

		let tag = self.as_usize() & TAG_MASK;

		match tag {
			0b00 => Tag::Simple,
			0b01 => Tag::Inline,

			#[cfg(feature = "alloc")]
			0b10 => Tag::Custom,

			#[cfg(feature = "std")]
			0b11 => Tag::Os,

			_ => unreachable!("tag should not be constructable"),
		}
	}

	/// Reinterprets the raw error as a [`usize`] value.
	#[inline]
	#[must_use]
	pub fn as_usize(&self) -> usize {
		self.0.addr().get()
	}
}

impl Debug for Raw {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(&self.data(), f)
	}
}

// SAFETY: All Raw fields must be `Send` as well.
unsafe impl Send for Raw {}

// SAFETY: All Raw fields must be `Sync` as well.
unsafe impl Sync for Raw {}

/// A tag for the raw error data.
#[repr(usize)]
enum Tag {
	/// A [`Simple`] error.
	///
	/// [`Simple`]: ErrorData::Simple
	Simple = 0b00,

	/// A [`Inline`] error.
	///
	/// [`Inline`]: ErrorData::Inline
	Inline = 0b01,

	/// A [`Custom`] error.
	///
	/// [`Custom`]: ErrorData::Custom
	#[cfg(feature = "alloc")]
	Custom = 0b10,

	/// A [`Os`] error.
	///
	/// [`Os`]: ErrorData::Os
	#[cfg(feature = "std")]
	Os = 0b11,
}

impl Tag {
	/// The amount of bits in any tag.
	pub const BITS: usize = 2;
}