use core::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull};
use alloc::{boxed::Box, string::String, vec::Vec};
use crate::{ZStr, ZStringError};
#[repr(transparent)]
#[cfg_attr(docs_rs, doc(cfg(feature = "alloc")))]
pub struct ZString {
pub(crate) nn: NonNull<u8>,
}
impl Drop for ZString {
#[inline]
fn drop(&mut self) {
let len = 1 + self.bytes().count();
let slice_ptr: *mut [u8] =
core::ptr::slice_from_raw_parts_mut(self.nn.as_ptr(), len);
drop(unsafe { Box::from_raw(slice_ptr) })
}
}
impl Clone for ZString {
#[inline]
#[must_use]
fn clone(&self) -> Self {
let len = 1 + self.bytes().count();
let slice_ptr: &[u8] =
unsafe { core::slice::from_raw_parts(self.nn.as_ptr(), len) };
let vec = Vec::from(slice_ptr);
let string = unsafe { String::from_utf8_unchecked(vec) };
let boxed_str = string.into_boxed_str();
unsafe { Self::new_unchecked(boxed_str) }
}
}
impl ZString {
#[inline]
#[must_use]
pub unsafe fn new_unchecked(b: Box<str>) -> Self {
let p: *mut u8 = Box::leak(b).as_mut_ptr();
let nn: NonNull<u8> = unsafe { NonNull::new_unchecked(p) };
Self { nn }
}
#[inline]
#[must_use]
pub const fn as_zstr(&self) -> ZStr<'_> {
ZStr { nn: self.nn, life: PhantomData }
}
#[inline]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.nn.as_ptr()
}
#[inline]
pub fn bytes(&self) -> impl Iterator<Item = u8> + '_ {
self.as_zstr().bytes()
}
#[inline]
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
self.as_zstr().chars()
}
}
impl From<ZStr<'_>> for ZString {
#[inline]
#[must_use]
fn from(value: ZStr<'_>) -> Self {
let other: ManuallyDrop<ZString> =
ManuallyDrop::new(ZString { nn: value.nn });
let other_ref: &ZString = &other;
other_ref.clone()
}
}
impl FromIterator<char> for ZString {
#[inline]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
let iter = iter.into_iter();
let no_nulls = iter.map(|ch| {
assert_ne!(ch, '\0');
ch
});
let null_on_the_end = no_nulls.chain(['\0']);
let s = String::from_iter(null_on_the_end);
unsafe { ZString::new_unchecked(s.into_boxed_str()) }
}
}
impl TryFrom<&str> for ZString {
type Error = ZStringError;
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
let trimmed = value.trim_end_matches('\0');
if trimmed.contains('\0') {
Err(ZStringError::InteriorNulls)
} else {
Ok(trimmed.chars().collect())
}
}
}
impl core::fmt::Display for ZString {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.as_zstr(), f)
}
}
impl core::fmt::Debug for ZString {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.as_zstr(), f)
}
}
impl core::fmt::Pointer for ZString {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Pointer::fmt(&self.as_zstr(), f)
}
}
impl PartialEq<ZString> for ZString {
#[inline]
#[must_use]
fn eq(&self, other: &ZString) -> bool {
self.as_zstr().eq(&other.as_zstr())
}
}
impl PartialOrd<ZString> for ZString {
#[inline]
#[must_use]
fn partial_cmp(&self, other: &ZString) -> Option<core::cmp::Ordering> {
self.as_zstr().partial_cmp(&other.as_zstr())
}
}
impl PartialEq<&str> for ZString {
#[inline]
#[must_use]
fn eq(&self, other: &&str) -> bool {
self.bytes().eq(other.as_bytes().iter().copied())
}
}
impl PartialOrd<&str> for ZString {
#[inline]
#[must_use]
fn partial_cmp(&self, other: &&str) -> Option<core::cmp::Ordering> {
Some(self.bytes().cmp(other.as_bytes().iter().copied()))
}
}
impl PartialEq<ZStr<'_>> for ZString {
#[inline]
#[must_use]
fn eq(&self, other: &ZStr<'_>) -> bool {
self.as_zstr().eq(other)
}
}
impl PartialOrd<ZStr<'_>> for ZString {
#[inline]
#[must_use]
fn partial_cmp(&self, other: &ZStr<'_>) -> Option<core::cmp::Ordering> {
self.as_zstr().partial_cmp(other)
}
}
impl core::hash::Hash for ZString {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
for b in self.bytes() {
state.write_u8(b)
}
}
}
#[inline]
#[must_use]
pub fn zstrings_as_zstrs(zstrings: &[ZString]) -> &[ZStr<'_>] {
unsafe {
core::slice::from_raw_parts(zstrings.as_ptr().cast(), zstrings.len())
}
}