nvec 0.10.0

N-vectors and N-strings.
Documentation
// Copyright 2025-2026 Gabriel Bjørnager Jensen.
//
// SPDX: MIT OR Apache-2.0

//! Conversion facilities for [`NString`].

use core::mem::MaybeUninit;
use core::str;
use nvec::NVec;
use nvec::n_string::{FromUtf8Error, NString};
use nvec::n_vec::TryReserveError;

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

#[cfg(feature = "alloc")]
use alloc::string::String;

#[cfg(feature = "std")]
use std::ffi::OsStr;

#[cfg(feature = "std")]
use std::path::Path;

impl<const N: usize> NString<N> {
	/// Constructs a new N-string from a string slice.
	///
	/// # Errors
	///
	/// If the length of the provided string is longer
	/// than the capacity of the N-string (i.e. `N`
	/// octets,) this constructor will return an [`Err`]
	/// instance.
	#[inline]
	pub const fn try_from_str(s: &str) -> Result<Self, TryReserveError> {
		if s.len() > N {
			return Err(TryReserveError);
		}

		// SAFETY: We have tested that the capacity is
		// great enough.
		let new = unsafe { Self::from_str_unchecked(s) };

		Ok(new)
	}

	/// Unsafely constructs a new N-string from a string
	/// slice.
	///
	/// # Safety
	///
	/// The provided string slice mustn't be longer than
	/// the capacity of the N-string.
	#[inline]
	#[must_use]
	pub const unsafe fn from_str_unchecked(s: &str) -> Self {
		let mut new = Self::new();

		// SAFETY:
		//
		// * The source pointer, deriving from a reference,
		//   is guaranteed to be valid for reads. The length
		//   comes from the same reference and guarantees
		//   the object size.
		//
		// * The destination pointer, deriving from a muta-
		//   ble reference, is guaranteed to be valid for
		//   writes. The caller guarantees that capacity is
		//   enough to contain the source string.
		//
		// * Both pointers, deriving from references, are
		//   guaranteed to be aligned.
		//
		// * The destination pointer, deriving from a muta-
		//   ble reference, is guaranteed to be exclusive
		//   at its creation. The source pointer does not
		//   derive from this destination pointer.
		unsafe { new.as_mut_ptr().copy_from_nonoverlapping(s.as_ptr(), s.len()) };

		// SAFETY: `str` has the same UTF-8 invariants as
		// we do. Caller guarantees that this length is
		// within bounds.
		unsafe { new.set_len(s.len()) };

		new
	}

	/// Attempts to construct an N-string from UTF-8.
	///
	/// # Errors
	///
	/// If the provided bytes do not represent a valid
	/// UTF-8 sequence, this constructor will return an
	/// [`Err`] instance.
	pub const fn from_utf8(bytes: NVec<u8, N>) -> Result<Self, FromUtf8Error<N>> {
		match str::from_utf8(bytes.as_slice()) {
			Ok(..) => {
				// SAFETY: The above call guarantees the UTF-8 in-
				// variants that we require ourselves.
				let s = unsafe { Self::from_utf8_unchecked(bytes) };

				Ok(s)
			}

			Err(utf8_error) => {
				let e = FromUtf8Error {
					error: utf8_error,

					bytes,
				};

				Err(e)
			}
		}
	}

	/// Constructs an N-string from UTF-8.
	///
	/// # Safety
	///
	/// The (active) elements of the provided N-vector
	/// must constitute a valid UTF-8 sequence.
	#[inline]
	#[must_use]
	pub const unsafe fn from_utf8_unchecked(bytes: NVec<u8, N>) -> Self {
		debug_assert!(str::from_utf8(bytes.as_slice()).is_ok());

		// SAFETY: Caller guarantees that the vector repre-
		// sents a valid UTF-8 sequence.
		Self(bytes)
	}

	/// Constructs an N-string from raw parts.
	///
	/// # Safety
	///
	/// The following prerequisites must be upheld when
	/// calling this constructor:
	///
	/// * `len` may not be greater than `N`.
	/// * The first `len`-count octets of `data` must
	///   denote a valid UTF-8 sequence.
	///
	/// Failure to upheld these invariants will yield
	/// undefined behaviour.
	#[inline(always)]
	#[must_use]
	pub const unsafe fn from_raw_parts(data: [MaybeUninit<u8>; N], len: usize) -> Self {
		// SAFETY: Our invariants are a superset of the
		// invariants of `NVec`.
		let bytes = unsafe { NVec::from_raw_parts(data, len) };

		// Caller guarantees that the buffer represents a
		// valid UTF-8 sequence.
		unsafe { Self::from_utf8_unchecked(bytes) }
	}

	/// Reinterprets the N-string as an N-vector.
	///
	/// # Safety
	///
	/// When the returned reference dies, the contents
	/// of the string must remain valid (i.e. denote a
	/// valid UTF-8 sequence.)
	#[inline(always)]
	#[must_use]
	pub const fn as_mut_vec(&mut self) -> &mut NVec<u8, N> {
		&mut self.0
	}

	/// Gets a string slice over the string.
	#[inline]
	#[must_use]
	pub const fn as_str(&self) -> &str {
		let bytes = self.as_bytes();

		// SAFETY: We already require valid UTF-8; thus
		// `as_bytes` guarantees the same.
		unsafe { str::from_utf8_unchecked(bytes) }
	}

	/// Gets a mutable string slice over the string.
	#[inline]
	#[must_use]
	pub const fn as_mut_str(&mut self) -> &mut str {
		// SAFETY: This reference is immediately converted
		// to a `str` reference, after which no invalidat-
		// ing write can be made (due to our having the
		// same UTF-8 invariants as `str`.)
		let bytes = unsafe { self.as_bytes_mut() };

		// SAFETY: We already require valid UTF-8; thus
		// `as_bytes_mut` guarantees the same.
		unsafe { str::from_utf8_unchecked_mut(bytes) }
	}

	/// Gets a slice over the string bytes.
	#[inline]
	#[must_use]
	pub const fn as_bytes(&self) -> &[u8] {
		self.0.as_slice()
	}

	/// Gets a mutable slice over the vector elements.
	///
	/// # Safety
	///
	/// The string octets must remain a valid UTF-8
	/// sequence after the returned slice dies.
	#[inline]
	#[must_use]
	pub const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
		self.0.as_mut_slice()
	}

	/// Gets a constant pointer to the first element.
	#[inline]
	#[must_use]
	pub const fn as_ptr(&self) -> *const u8 {
		self.0.as_ptr()
	}

	/// Gets a mutable pointer to the first element.
	#[inline]
	#[must_use]
	pub const fn as_mut_ptr(&mut self) -> *mut u8 {
		self.0.as_mut_ptr()
	}

	/// Converts the N-string to an N-vector of bytes.
	#[inline]
	#[must_use]
	pub const fn to_bytes(&self) -> NVec<u8, N> {
		self.0.copied()
	}

	/// Converts the N-string to a standard string.
	#[cfg(feature = "alloc")]
	#[inline]
	#[must_use]
	pub fn to_string(&self) -> String {
		let bytes = self.to_bytes().into_vec();

		// SAFETY: `String` has the same UTF-8 invariants
		// as us.
		unsafe { String::from_utf8_unchecked(bytes) }
	}

	/// Converts the N-string to a boxed string.
	#[cfg(feature = "alloc")]
	#[inline]
	#[must_use]
	pub fn to_boxed_str(&self) -> Box<str> {
		self.to_string().into()
	}

	/// Deconstructs the N-string into its raw parts.
	///
	/// # Safety
	///
	/// The following guarantees are made by this
	/// destructor:
	///
	/// * The first `len`-count octets of the returned
	///   buffer denote a valid UTF-8 sequence.
	/// * The length
	#[inline(always)]
	#[must_use]
	pub const fn to_raw_parts(&self) -> ([MaybeUninit<u8>; N], usize) {
		self.to_bytes().into_raw_parts()
	}
}

impl<const N: usize> AsMut<str> for NString<N> {
	#[inline]
	fn as_mut(&mut self) -> &mut str {
		self
	}
}

#[cfg(feature = "std")]
impl<const N: usize> AsRef<Path> for NString<N> {
	#[inline]
	fn as_ref(&self) -> &Path {
		self.as_str().as_ref()
	}
}

#[cfg(feature = "std")]
impl<const N: usize> AsRef<OsStr> for NString<N> {
	#[inline]
	fn as_ref(&self) -> &OsStr {
		self.as_str().as_ref()
	}
}

// SORT: slice
#[cfg(feature = "std")]
impl<const N: usize> AsRef<[u8]> for NString<N> {
	#[inline]
	fn as_ref(&self) -> &[u8] {
		self.as_bytes()
	}
}

impl<const N: usize> AsRef<str> for NString<N> {
	#[inline]
	fn as_ref(&self) -> &str {
		self
	}
}

impl<const N: usize> TryFrom<char> for NString<N> {
	type Error = TryReserveError;

	#[inline]
	fn try_from(value: char) -> Result<Self, Self::Error> {
		let mut buf = [0; 4];
		value.encode_utf8(&mut buf).try_into()
	}
}

impl<const N: usize> TryFrom<NVec<u8, N>> for NString<N> {
	type Error = FromUtf8Error<N>;

	/// See [`NString::from_utf8`].
	#[inline]
	fn try_from(value: NVec<u8, N>) -> Result<Self, Self::Error> {
		Self::from_utf8(value)
	}
}

impl<const N: usize> TryFrom<&str> for NString<N> {
	type Error = TryReserveError;

	/// See [`NString::try_from_str`].
	#[inline]
	fn try_from(value: &str) -> Result<Self, Self::Error> {
		Self::try_from_str(value)
	}
}

impl<const N: usize> TryFrom<&mut str> for NString<N> {
	type Error = TryReserveError;

	/// See [`NString::try_from_str`].
	#[inline]
	fn try_from(value: &mut str) -> Result<Self, Self::Error> {
		Self::try_from_str(value)
	}
}

#[cfg(feature = "alloc")]
impl<const N: usize> From<NString<N>> for Box<str> {
	#[inline]
	fn from(value: NString<N>) -> Self {
		value.to_boxed_str()
	}
}

#[cfg(feature = "alloc")]
impl<const N: usize> From<NString<N>> for String {
	#[inline]
	fn from(value: NString<N>) -> Self {
		value.to_string()
	}
}