#[cfg(feature = "std")]
use std::{ffi::OsStr, path::Path};
use core::{
cmp,
ffi::{CStr, FromBytesWithNulError},
fmt,
ops::{Deref, Index},
slice::SliceIndex,
str::{self, Utf8Error},
};
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CStr8 {
raw: str,
}
impl Deref for CStr8 {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl Default for &'_ CStr8 {
fn default() -> Self {
cstr8!("")
}
}
impl fmt::Debug for CStr8 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl fmt::Display for CStr8 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl AsRef<str> for CStr8 {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<CStr> for CStr8 {
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl AsRef<[u8]> for CStr8 {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl PartialEq<str> for CStr8 {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<CStr8> for str {
fn eq(&self, other: &CStr8) -> bool {
self == other.as_str()
}
}
impl PartialOrd<str> for CStr8 {
fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
self.as_str().partial_cmp(other)
}
}
impl PartialOrd<CStr8> for str {
fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_str())
}
}
impl PartialEq<CStr> for CStr8 {
fn eq(&self, other: &CStr) -> bool {
self.as_c_str() == other
}
}
impl PartialEq<CStr8> for CStr {
fn eq(&self, other: &CStr8) -> bool {
self == other.as_c_str()
}
}
impl PartialOrd<CStr> for CStr8 {
fn partial_cmp(&self, other: &CStr) -> Option<cmp::Ordering> {
self.as_c_str().partial_cmp(other)
}
}
impl PartialOrd<CStr8> for CStr {
fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_c_str())
}
}
impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
type Error = CStr8Error;
fn try_from(s: &'a CStr) -> Result<&'a CStr8, CStr8Error> {
CStr8::from_utf8_with_nul(s.to_bytes_with_nul())
}
}
#[cfg(feature = "alloc")]
mod alloc_impls {
use {
crate::{CStr8, CString8},
alloc::{
borrow::{Cow, ToOwned},
boxed::Box,
ffi::CString,
rc::Rc,
string::String,
sync::Arc,
},
core::ffi::CStr,
};
impl From<&CStr8> for Arc<CStr8> {
fn from(s: &CStr8) -> Arc<CStr8> {
let arc = Arc::<[u8]>::from(s.as_bytes_with_nul());
unsafe { Arc::from_raw(Arc::into_raw(arc) as *const CStr8) }
}
}
impl From<&CStr8> for Arc<CStr> {
fn from(s: &CStr8) -> Arc<CStr> {
s.as_c_str().into()
}
}
impl From<&CStr8> for Arc<str> {
fn from(s: &CStr8) -> Arc<str> {
s.as_str().into()
}
}
impl From<&CStr8> for Box<CStr8> {
fn from(s: &CStr8) -> Box<CStr8> {
let boxed = Box::<[u8]>::from(s.as_bytes_with_nul());
unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr8) }
}
}
impl From<&CStr8> for Box<CStr> {
fn from(s: &CStr8) -> Box<CStr> {
s.as_c_str().into()
}
}
impl From<&CStr8> for Box<str> {
fn from(s: &CStr8) -> Box<str> {
s.as_str().into()
}
}
impl<'a> From<&'a CStr8> for Cow<'a, CStr8> {
fn from(s: &'a CStr8) -> Cow<'a, CStr8> {
Cow::Borrowed(s)
}
}
impl<'a> From<&'a CStr8> for Cow<'a, CStr> {
fn from(s: &'a CStr8) -> Cow<'a, CStr> {
s.as_c_str().into()
}
}
impl<'a> From<&'a CStr8> for Cow<'a, str> {
fn from(s: &'a CStr8) -> Cow<'a, str> {
s.as_str().into()
}
}
impl From<&CStr8> for Rc<CStr8> {
fn from(s: &CStr8) -> Rc<CStr8> {
let rc = Rc::<[u8]>::from(s.as_bytes_with_nul());
unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr8) }
}
}
impl From<&CStr8> for Rc<CStr> {
fn from(s: &CStr8) -> Rc<CStr> {
s.as_c_str().into()
}
}
impl From<&CStr8> for Rc<str> {
fn from(s: &CStr8) -> Rc<str> {
s.as_str().into()
}
}
impl From<&CStr8> for CString8 {
fn from(s: &CStr8) -> CString8 {
s.to_owned()
}
}
impl From<&CStr8> for CString {
fn from(s: &CStr8) -> CString {
s.as_c_str().into()
}
}
impl From<&CStr8> for String {
fn from(s: &CStr8) -> String {
s.as_str().into()
}
}
impl From<Cow<'_, CStr8>> for Box<CStr8> {
fn from(s: Cow<'_, CStr8>) -> Box<CStr8> {
match s {
Cow::Borrowed(s) => Box::from(s),
Cow::Owned(s) => Box::from(s),
}
}
}
impl PartialEq<String> for CStr8 {
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl PartialEq<CStr8> for String {
fn eq(&self, other: &CStr8) -> bool {
self == other.as_str()
}
}
impl ToOwned for CStr8 {
type Owned = CString8;
fn to_owned(&self) -> CString8 {
unsafe { CString8::from_vec_unchecked(self.as_bytes_with_nul().to_owned()) }
}
}
}
#[cfg(feature = "std")]
mod std_impls {
use {
crate::CStr8,
core::cmp,
std::{
ffi::{OsStr, OsString},
path::Path,
},
};
impl AsRef<OsStr> for CStr8 {
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
impl AsRef<Path> for CStr8 {
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}
impl PartialEq<OsStr> for CStr8 {
fn eq(&self, other: &OsStr) -> bool {
self.as_str() == other
}
}
impl PartialEq<OsString> for &'_ CStr8 {
fn eq(&self, other: &OsString) -> bool {
self.as_str() == other
}
}
impl PartialEq<OsString> for CStr8 {
fn eq(&self, other: &OsString) -> bool {
self.as_str() == other
}
}
impl PartialEq<CStr8> for OsStr {
fn eq(&self, other: &CStr8) -> bool {
self == other.as_str()
}
}
impl PartialEq<CStr8> for OsString {
fn eq(&self, other: &CStr8) -> bool {
self == other.as_str()
}
}
impl PartialOrd<CStr8> for OsStr {
fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_str())
}
}
impl PartialOrd<CStr8> for OsString {
fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
self.partial_cmp(other.as_str())
}
}
}
impl<I> Index<I> for CStr8
where
I: SliceIndex<str>,
{
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
&self.as_str()[index]
}
}
impl CStr8 {
pub const fn as_str(&self) -> &str {
match self.raw.as_bytes() {
[rest @ .., _nul] => unsafe { str::from_utf8_unchecked(rest) },
[] => unreachable!(),
}
}
pub const fn as_c_str(&self) -> &CStr {
unsafe { CStr::from_bytes_with_nul_unchecked(self.raw.as_bytes()) }
}
pub const fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
pub const fn as_bytes_with_nul(&self) -> &[u8] {
self.raw.as_bytes()
}
#[cfg(feature = "std")]
pub fn as_os_str(&self) -> &OsStr {
self.as_ref()
}
#[cfg(feature = "std")]
pub fn as_path(&self) -> &Path {
self.as_ref()
}
pub const fn as_ptr(&self) -> *const u8 {
self.as_bytes_with_nul().as_ptr()
}
}
impl CStr8 {
pub fn from_utf8_with_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
let _ = str::from_utf8(v)?;
let _ = CStr::from_bytes_with_nul(v)?;
Ok(unsafe { CStr8::from_utf8_with_nul_unchecked(v) })
}
pub fn from_utf8_until_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
let v = CStr::from_bytes_until_nul(v)
.map(CStr::to_bytes_with_nul)
.unwrap_or_default();
Self::from_utf8_with_nul(v)
}
pub const unsafe fn from_utf8_with_nul_unchecked(v: &[u8]) -> &CStr8 {
&*(v as *const [u8] as *const CStr8)
}
pub unsafe fn from_ptr<'a>(ptr: *const u8) -> &'a CStr8 {
CStr8::from_utf8_with_nul_unchecked(CStr::from_ptr(ptr.cast()).to_bytes_with_nul())
}
}
#[derive(Debug)]
pub enum CStr8Error {
InvalidUtf8(Utf8Error),
NulError(FromBytesWithNulError),
}
#[cfg(feature = "std")]
impl std::error::Error for CStr8Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CStr8Error::InvalidUtf8(source) => Some(source),
CStr8Error::NulError(source) => Some(source),
}
}
}
impl fmt::Display for CStr8Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CStr8Error::InvalidUtf8(_) => f.write_str("invalid UTF-8"),
CStr8Error::NulError(_) => f.write_str("invalid nul terminator"),
}
}
}
impl From<Utf8Error> for CStr8Error {
fn from(source: Utf8Error) -> Self {
CStr8Error::InvalidUtf8(source)
}
}
impl From<FromBytesWithNulError> for CStr8Error {
fn from(source: FromBytesWithNulError) -> Self {
CStr8Error::NulError(source)
}
}