1#![doc = include_str!("../README.md")]
5#![no_std]
6#![cfg_attr(all(doc, not(doctest), feature = "nightly"), feature(doc_auto_cfg))]
7#![deny(missing_docs)]
8
9#[cfg(feature = "alloc")]
10const MAX_STACK_LEN: usize = 4096;
11
12#[macro_export]
28macro_rules! c8 {
29 ($string:literal) => {{
30 const _: &::core::primitive::str = $string; const S: &$crate::C8Str =
32 $crate::__const_str_with_nul_to_c8_str(::core::concat!($string, "\0"));
33 S
34 }};
35}
36
37#[cfg(feature = "alloc")]
42#[macro_export]
43macro_rules! c8string {
44 ($string:literal) => {
45 $crate::C8String::from(c8!($string))
46 };
47}
48
49#[cfg(feature = "alloc")]
50#[macro_export]
55macro_rules! c8format {
56 ($($fmt_args:tt)*) => {
57 $crate::C8String::from_string($crate::__reexports::format!($($fmt_args)*)).unwrap()
58 };
59}
60
61use core::{convert::Infallible, error::Error, ffi::CStr};
62
63#[cfg(feature = "alloc")]
64extern crate alloc;
65
66#[cfg(feature = "alloc")]
67use core::mem::MaybeUninit;
68
69#[cfg(feature = "alloc")]
70use alloc::{ffi::CString, string::String};
71
72#[doc(hidden)]
73pub mod __reexports {
74 #[cfg(feature = "alloc")]
75 pub use alloc::format;
76}
77
78mod c8str;
79
80#[doc(inline)]
81pub use c8str::C8Str;
82
83#[cfg(feature = "alloc")]
84mod c8string;
85
86#[cfg(feature = "alloc")]
87#[doc(inline)]
88pub use c8string::{C8String, StringType};
89
90#[cfg(test)]
91mod tests;
92
93use core::{
94 fmt::{self, Debug, Display},
95 num::NonZeroU32,
96 slice, str,
97};
98
99#[doc(hidden)]
100pub const fn __const_str_with_nul_to_c8_str(str: &str) -> &C8Str {
102 match C8Str::from_str_with_nul(str) {
103 Ok(str) => str,
104 Err(e) => e.into_const_panic(),
105 }
106}
107
108const fn const_count_nonzero_bytes(bytes: &[u8]) -> usize {
109 let mut i = 0;
110 while i < bytes.len() {
111 if bytes[i] == 0 {
112 return i;
113 }
114 i += 1;
115 }
116 i
117}
118
119const fn const_byte_prefix(bytes: &[u8], len: usize) -> &[u8] {
120 debug_assert!(len <= bytes.len());
121 unsafe { slice::from_raw_parts(bytes.as_ptr(), len) }
122}
123
124const fn const_str_without_null_terminator(str: &str) -> &str {
125 debug_assert!(str.as_bytes()[str.len() - 1] == 0);
126 unsafe { str::from_utf8_unchecked(const_byte_prefix(str.as_bytes(), str.len() - 1)) }
127}
128
129const fn const_nonzero_bytes_with_null_terminator(bytes: &[u8]) -> Option<&[u8]> {
130 let nonzero_len = const_count_nonzero_bytes(bytes);
131 if nonzero_len < bytes.len() {
132 Some(const_byte_prefix(bytes, nonzero_len + 1))
133 } else {
134 None
135 }
136}
137
138const fn const_min(a: usize, b: usize) -> usize {
139 if a < b {
140 a
141 } else {
142 b
143 }
144}
145
146#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub struct C8StrError(C8StrErrorEnum);
149
150impl C8StrError {
151 #[inline]
152 const fn not_utf8(at: usize) -> Self {
153 Self(C8StrErrorEnum::NotUtf8(
154 const_min(at, u32::MAX as usize) as u32
155 ))
156 }
157
158 #[inline]
159 const fn inner_zero(at: usize) -> Self {
160 Self(C8StrErrorEnum::InnerZero(
161 const_min(at, u32::MAX as usize) as u32
162 ))
163 }
164
165 #[allow(unused)] #[inline]
167 const fn inner_zero_unknown() -> Self {
168 Self(C8StrErrorEnum::InnerZero(u32::MAX))
169 }
170
171 #[inline]
172 const fn missing_terminator() -> Self {
173 Self(C8StrErrorEnum::MissingTerminator)
174 }
175
176 #[inline]
178 const fn into_const_panic(self) -> ! {
179 match self.0 {
180 C8StrErrorEnum::NotUtf8(_) => panic!("string isn't valid utf-8"),
181 C8StrErrorEnum::InnerZero(_) => {
182 panic!("string contains null bytes before the end")
183 }
184 C8StrErrorEnum::MissingTerminator => {
185 panic!("string doesn't have a null terminator")
186 }
187 }
188 }
189}
190
191#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
192enum C8StrErrorEnum {
193 NotUtf8(u32),
194 InnerZero(u32),
195 MissingTerminator,
196}
197
198impl Debug for C8StrError {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 write!(f, "C8StrError(\"{self}\")")
201 }
202}
203
204impl Display for C8StrError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 match &self.0 {
207 C8StrErrorEnum::NotUtf8(i) => {
208 write!(f, "invalid utf-8")?;
209 if *i != u32::MAX {
210 write!(f, " at {i}")?;
211 }
212 Ok(())
213 }
214 C8StrErrorEnum::InnerZero(i) => {
215 write!(f, "null byte before the end")?;
216 if *i != u32::MAX {
217 write!(f, " at {i}")?;
218 }
219 Ok(())
220 }
221 C8StrErrorEnum::MissingTerminator => {
222 write!(f, "missing null terminator")
223 }
224 }
225 }
226}
227
228impl Error for C8StrError {}
229
230#[non_exhaustive]
232#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
233pub struct NonZeroCharError;
234
235impl Debug for NonZeroCharError {
236 #[inline]
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 f.write_str("NonZeroCharError")
239 }
240}
241
242impl Display for NonZeroCharError {
243 #[inline]
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 f.write_str("nonzero char")
246 }
247}
248
249impl Error for NonZeroCharError {}
250
251#[repr(transparent)]
253#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
254pub struct NonZeroChar(NonZeroU32);
255
256impl NonZeroChar {
257 #[inline]
259 pub const fn new(ch: char) -> Option<Self> {
260 match NonZeroU32::new(ch as u32) {
261 Some(ch) => Some(Self(ch)),
262 None => None,
263 }
264 }
265
266 #[inline]
271 pub const unsafe fn new_unchecked(ch: char) -> Self {
272 unsafe { Self(NonZeroU32::new_unchecked(ch as u32)) }
273 }
274
275 #[inline]
277 pub const fn get(self) -> char {
278 unsafe {
279 char::from_u32_unchecked(self.0.get())
281 }
282 }
283}
284
285impl Debug for NonZeroChar {
286 #[inline]
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 write!(f, "NonZeroChar({:?})", self.get())
289 }
290}
291
292impl Display for NonZeroChar {
293 #[inline]
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 <char as Display>::fmt(&self.get(), f)
296 }
297}
298
299impl TryFrom<char> for NonZeroChar {
300 type Error = NonZeroCharError;
301
302 #[inline]
303 fn try_from(value: char) -> Result<Self, Self::Error> {
304 Self::new(value).ok_or(NonZeroCharError)
305 }
306}
307
308impl From<NonZeroChar> for char {
309 #[inline]
310 fn from(value: NonZeroChar) -> Self {
311 value.get()
312 }
313}
314
315pub trait WithC8Str: Sized {
317 type Error;
319
320 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error>;
322}
323
324impl<'a, T: ?Sized> WithC8Str for &&'a T
325where
326 &'a T: WithC8Str,
327{
328 type Error = <&'a T as WithC8Str>::Error;
329
330 #[inline(always)]
331 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
332 (*self).with_c8_str(f)
333 }
334}
335
336impl<'a, T: ?Sized> WithC8Str for &mut &'a T
337where
338 &'a T: WithC8Str,
339{
340 type Error = <&'a T as WithC8Str>::Error;
341
342 #[inline(always)]
343 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
344 (*self).with_c8_str(f)
345 }
346}
347
348impl<'a, T: ?Sized> WithC8Str for &'a &'a mut T
349where
350 &'a T: WithC8Str,
351{
352 type Error = <&'a T as WithC8Str>::Error;
353
354 #[inline(always)]
355 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
356 (self as &T).with_c8_str(f)
357 }
358}
359
360impl<'a, T: ?Sized> WithC8Str for &'a mut &'a mut T
361where
362 &'a T: WithC8Str,
363{
364 type Error = <&'a T as WithC8Str>::Error;
365
366 #[inline(always)]
367 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
368 (self as &T).with_c8_str(f)
369 }
370}
371
372impl WithC8Str for &C8Str {
373 type Error = Infallible;
374
375 #[inline(always)]
376 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
377 Ok(f(self))
378 }
379}
380
381impl WithC8Str for &mut C8Str {
382 type Error = Infallible;
383
384 #[inline(always)]
385 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
386 Ok(f(self))
387 }
388}
389
390#[cfg(feature = "alloc")]
391impl WithC8Str for C8String {
392 type Error = Infallible;
393
394 #[inline(always)]
395 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
396 Ok(f(&self))
397 }
398}
399
400#[cfg(feature = "alloc")]
401impl WithC8Str for &C8String {
402 type Error = Infallible;
403
404 #[inline(always)]
405 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
406 Ok(f(self))
407 }
408}
409
410#[cfg(feature = "alloc")]
411impl WithC8Str for &mut C8String {
412 type Error = Infallible;
413
414 #[inline(always)]
415 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
416 Ok(f(self))
417 }
418}
419
420impl WithC8Str for &CStr {
421 type Error = C8StrError;
422
423 #[inline(always)]
424 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
425 Ok(f(C8Str::from_c_str(self)?))
426 }
427}
428
429impl WithC8Str for &mut CStr {
430 type Error = C8StrError;
431
432 #[inline(always)]
433 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
434 (&*self).with_c8_str(f)
435 }
436}
437
438#[cfg(feature = "alloc")]
439impl WithC8Str for CString {
440 type Error = C8StrError;
441
442 #[inline(always)]
443 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
444 Ok(f(&C8String::from_c_string(self)?))
445 }
446}
447
448#[cfg(feature = "alloc")]
449impl WithC8Str for &CString {
450 type Error = C8StrError;
451
452 #[inline(always)]
453 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
454 Ok(f(C8Str::from_c_str(self)?))
455 }
456}
457
458#[cfg(feature = "alloc")]
459impl WithC8Str for &mut CString {
460 type Error = C8StrError;
461
462 #[inline(always)]
463 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
464 Ok(f(C8Str::from_c_str(self)?))
465 }
466}
467
468#[cfg(feature = "alloc")]
469impl WithC8Str for &str {
470 type Error = C8StrError;
471
472 #[inline(always)]
473 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
474 if self.len() < MAX_STACK_LEN {
475 let mut buf: [MaybeUninit<u8>; MAX_STACK_LEN] =
476 [const { MaybeUninit::uninit() }; MAX_STACK_LEN];
477 let buf = unsafe {
478 let buf_ptr = buf.as_mut_ptr().cast::<u8>();
479 self.as_bytes()
480 .as_ptr()
481 .copy_to_nonoverlapping(buf_ptr, self.len());
482 buf_ptr.add(self.len()).write(0);
483 slice::from_raw_parts(buf_ptr, self.len() + 1)
484 };
485 Ok(f(C8Str::from_bytes_with_nul(buf)?))
486 } else {
487 Ok(f(&C8String::from_string(self)?))
488 }
489 }
490}
491
492#[cfg(feature = "alloc")]
493impl WithC8Str for &mut str {
494 type Error = C8StrError;
495
496 #[inline(always)]
497 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
498 (&*self).with_c8_str(f)
499 }
500}
501
502#[cfg(feature = "alloc")]
503impl WithC8Str for String {
504 type Error = C8StrError;
505
506 #[inline(always)]
507 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
508 Ok(f(&C8String::from_string(self)?))
509 }
510}
511
512#[cfg(feature = "alloc")]
513impl WithC8Str for &String {
514 type Error = C8StrError;
515
516 #[inline(always)]
517 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
518 Ok(f(&C8String::from_string(self.as_str())?))
519 }
520}
521
522#[cfg(feature = "alloc")]
523impl WithC8Str for &mut String {
524 type Error = C8StrError;
525
526 #[inline(always)]
527 fn with_c8_str<R>(self, f: impl FnOnce(&C8Str) -> R) -> Result<R, Self::Error> {
528 Ok(f(&C8String::from_string(self.as_str())?))
529 }
530}