#![doc = include_str!("../README.md")]
#![no_std]
#![cfg_attr(all(doc, not(doctest), feature = "nightly"), feature(doc_auto_cfg))]
#![deny(missing_docs)]
#[cfg(feature = "alloc")]
const MAX_STACK_LEN: usize = 4096;
#[macro_export]
macro_rules! c8 {
($string:literal) => {{
const _: &::core::primitive::str = $string; const S: &$crate::C8Str =
$crate::__const_str_with_nul_to_c8_str(::core::concat!($string, "\0"));
S
}};
}
#[cfg(feature = "alloc")]
#[macro_export]
macro_rules! c8string {
($string:literal) => {
$crate::C8String::from(c8!($string))
};
}
#[cfg(feature = "alloc")]
#[macro_export]
macro_rules! c8format {
($($fmt_args:tt)*) => {
$crate::C8String::from_string($crate::__reexports::format!($($fmt_args)*)).unwrap()
};
}
use core::{convert::Infallible, error::Error, ffi::CStr};
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use core::mem::MaybeUninit;
#[cfg(feature = "alloc")]
use alloc::{ffi::CString, string::String};
#[doc(hidden)]
pub mod __reexports {
#[cfg(feature = "alloc")]
pub use alloc::format;
}
mod c8str;
#[doc(inline)]
pub use c8str::C8Str;
#[cfg(feature = "alloc")]
mod c8string;
#[cfg(feature = "alloc")]
#[doc(inline)]
pub use c8string::{C8String, StringType};
#[cfg(test)]
mod tests;
use core::{
fmt::{self, Debug, Display},
num::NonZeroU32,
slice, str,
};
#[doc(hidden)]
pub const fn __const_str_with_nul_to_c8_str(str: &str) -> &C8Str {
match C8Str::from_str_with_nul(str) {
Ok(str) => str,
Err(e) => e.into_const_panic(),
}
}
const fn const_count_nonzero_bytes(bytes: &[u8]) -> usize {
let mut i = 0;
while i < bytes.len() {
if bytes[i] == 0 {
return i;
}
i += 1;
}
i
}
const fn const_byte_prefix(bytes: &[u8], len: usize) -> &[u8] {
debug_assert!(len <= bytes.len());
unsafe { slice::from_raw_parts(bytes.as_ptr(), len) }
}
const fn const_str_without_null_terminator(str: &str) -> &str {
debug_assert!(str.as_bytes()[str.len() - 1] == 0);
unsafe { str::from_utf8_unchecked(const_byte_prefix(str.as_bytes(), str.len() - 1)) }
}
const fn const_nonzero_bytes_with_null_terminator(bytes: &[u8]) -> Option<&[u8]> {
let nonzero_len = const_count_nonzero_bytes(bytes);
if nonzero_len < bytes.len() {
Some(const_byte_prefix(bytes, nonzero_len + 1))
} else {
None
}
}
const fn const_min(a: usize, b: usize) -> usize {
if a < b {
a
} else {
b
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct C8StrError(C8StrErrorEnum);
impl C8StrError {
#[inline]
const fn not_utf8(at: usize) -> Self {
Self(C8StrErrorEnum::NotUtf8(
const_min(at, u32::MAX as usize) as u32
))
}
#[inline]
const fn inner_zero(at: usize) -> Self {
Self(C8StrErrorEnum::InnerZero(
const_min(at, u32::MAX as usize) as u32
))
}
#[allow(unused)] #[inline]
const fn inner_zero_unknown() -> Self {
Self(C8StrErrorEnum::InnerZero(u32::MAX))
}
#[inline]
const fn missing_terminator() -> Self {
Self(C8StrErrorEnum::MissingTerminator)
}
#[inline]
const fn into_const_panic(self) -> ! {
match self.0 {
C8StrErrorEnum::NotUtf8(_) => panic!("string isn't valid utf-8"),
C8StrErrorEnum::InnerZero(_) => {
panic!("string contains null bytes before the end")
}
C8StrErrorEnum::MissingTerminator => {
panic!("string doesn't have a null terminator")
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum C8StrErrorEnum {
NotUtf8(u32),
InnerZero(u32),
MissingTerminator,
}
impl Debug for C8StrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "C8StrError(\"{self}\")")
}
}
impl Display for C8StrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
C8StrErrorEnum::NotUtf8(i) => {
write!(f, "invalid utf-8")?;
if *i != u32::MAX {
write!(f, " at {i}")?;
}
Ok(())
}
C8StrErrorEnum::InnerZero(i) => {
write!(f, "null byte before the end")?;
if *i != u32::MAX {
write!(f, " at {i}")?;
}
Ok(())
}
C8StrErrorEnum::MissingTerminator => {
write!(f, "missing null terminator")
}
}
}
}
impl Error for C8StrError {}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonZeroCharError;
impl Debug for NonZeroCharError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("NonZeroCharError")
}
}
impl Display for NonZeroCharError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("nonzero char")
}
}
impl Error for NonZeroCharError {}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonZeroChar(NonZeroU32);
impl NonZeroChar {
#[inline]
pub const fn new(ch: char) -> Option<Self> {
match NonZeroU32::new(ch as u32) {
Some(ch) => Some(Self(ch)),
None => None,
}
}
#[inline]
pub const unsafe fn new_unchecked(ch: char) -> Self {
unsafe { Self(NonZeroU32::new_unchecked(ch as u32)) }
}
#[inline]
pub const fn get(self) -> char {
unsafe {
char::from_u32_unchecked(self.0.get())
}
}
}
impl Debug for NonZeroChar {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NonZeroChar({:?})", self.get())
}
}
impl Display for NonZeroChar {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<char as Display>::fmt(&self.get(), f)
}
}
impl TryFrom<char> for NonZeroChar {
type Error = NonZeroCharError;
#[inline]
fn try_from(value: char) -> Result<Self, Self::Error> {
Self::new(value).ok_or(NonZeroCharError)
}
}
impl From<NonZeroChar> for char {
#[inline]
fn from(value: NonZeroChar) -> Self {
value.get()
}
}
pub trait WithC8Str: Sized {
type Error;
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error>;
}
impl<'a, T: ?Sized> WithC8Str for &&'a T
where
&'a T: WithC8Str,
{
type Error = <&'a T as WithC8Str>::Error;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(*self).with_c8_str(f)
}
}
impl<'a, T: ?Sized> WithC8Str for &mut &'a T
where
&'a T: WithC8Str,
{
type Error = <&'a T as WithC8Str>::Error;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(*self).with_c8_str(f)
}
}
impl<'a, T: ?Sized> WithC8Str for &'a &'a mut T
where
&'a T: WithC8Str,
{
type Error = <&'a T as WithC8Str>::Error;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(self as &T).with_c8_str(f)
}
}
impl<'a, T: ?Sized> WithC8Str for &'a mut &'a mut T
where
&'a T: WithC8Str,
{
type Error = <&'a T as WithC8Str>::Error;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(self as &T).with_c8_str(f)
}
}
impl WithC8Str for &C8Str {
type Error = Infallible;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(self))
}
}
impl WithC8Str for &mut C8Str {
type Error = Infallible;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(self))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for C8String {
type Error = Infallible;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(&self))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &C8String {
type Error = Infallible;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(self))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &mut C8String {
type Error = Infallible;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(self))
}
}
impl WithC8Str for &CStr {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(C8Str::from_c_str(self)?))
}
}
impl WithC8Str for &mut CStr {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(&*self).with_c8_str(f)
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for CString {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(&C8String::from_c_string(self)?))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &CString {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(C8Str::from_c_str(self)?))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &mut CString {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(C8Str::from_c_str(self)?))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &str {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
if self.len() < MAX_STACK_LEN {
let mut buf: [MaybeUninit<u8>; MAX_STACK_LEN] =
[const { MaybeUninit::uninit() }; MAX_STACK_LEN];
let buf = unsafe {
let buf_ptr = buf.as_mut_ptr().cast::<u8>();
self.as_bytes()
.as_ptr()
.copy_to_nonoverlapping(buf_ptr, self.len());
buf_ptr.add(self.len()).write(0);
slice::from_raw_parts(buf_ptr, self.len() + 1)
};
Ok(f(C8Str::from_bytes_with_nul(buf)?))
} else {
Ok(f(&C8String::from_string(self)?))
}
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &mut str {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
(&*self).with_c8_str(f)
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for String {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(&C8String::from_string(self)?))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &String {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(&C8String::from_string(self.as_str())?))
}
}
#[cfg(feature = "alloc")]
impl WithC8Str for &mut String {
type Error = C8StrError;
#[inline(always)]
fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
Ok(f(&C8String::from_string(self.as_str())?))
}
}