1use core::slice;
2use core::{
3 ffi::CStr,
4 fmt::{self, Debug},
5 num::NonZeroU8,
6 ops::Deref,
7};
8
9use crate::error::{CStrLenError, InteriorNulError};
10use NulByte::Nul;
11
12#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
14#[repr(u8)]
15enum NulByte {
16 Nul = 0,
17}
18
19const fn count_bytes(val: &CStr) -> usize {
20 let mut p = val.as_ptr();
22 let mut other_len = 0;
23 while unsafe { *p } != 0 {
24 other_len += 1;
25 p = unsafe { p.add(1) };
26 }
27 other_len
28}
29
30#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
38#[repr(C)]
39pub struct CStrArray<const N: usize> {
40 data: [NonZeroU8; N],
41 nul: NulByte,
42}
43
44impl<const N: usize> CStrArray<N> {
45 pub const fn new(val: &CStr) -> Result<Self, CStrLenError<N>> {
55 let other_len = count_bytes(val);
56 if other_len != N {
57 return Err(CStrLenError { src_len: other_len });
58 }
59 Ok(unsafe { Self::new_unchecked(val) })
61 }
62
63 pub const unsafe fn new_unchecked(val: &CStr) -> Self {
69 CStrArray {
71 data: unsafe { *val.as_ptr().cast() },
72 nul: Nul,
73 }
74 }
75
76 pub const fn from_bytes_without_nul(bytes: &[u8; N]) -> Result<Self, InteriorNulError> {
86 let mut i = 0;
88 while i < N {
89 if bytes[i] == 0 {
90 return Err(InteriorNulError { position: i });
91 }
92 i += 1;
93 }
94 Ok(unsafe { Self::from_bytes_without_nul_unchecked(bytes) })
96 }
97
98 pub const unsafe fn from_bytes_without_nul_unchecked(
107 bytes: &[u8; N],
108 ) -> Self {
109 let nonzero: &[NonZeroU8; N] = unsafe { &*bytes.as_ptr().cast() };
111 Self {
112 data: *nonzero,
113 nul: Nul,
114 }
115 }
116
117 #[allow(clippy::len_without_is_empty)]
119 pub const fn len(&self) -> usize {
120 N
121 }
122
123 pub const fn as_c_str(&self) -> &CStr {
127 unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
131 }
132
133 pub const fn as_bytes(&self) -> &[u8; N] {
138 unsafe { &*self.data.as_ptr().cast() }
140 }
141
142 pub const fn as_bytes_with_nul(&self) -> &[u8] {
146 unsafe { slice::from_raw_parts(self as *const _ as *const u8, N + 1) }
151 }
152
153 pub const fn into_bytes(self) -> [u8; N] {
155 *self.as_bytes()
156 }
157
158 #[deprecated = "use `len`"]
163 pub const fn count_bytes(&self) -> usize {
164 self.len()
165 }
166
167 #[deprecated = "use `as_bytes`"]
172 pub const fn to_bytes(&self) -> &[u8] {
173 self.as_bytes()
174 }
175
176 #[deprecated = "use `as_bytes_with_nul`"]
181 pub const fn to_bytes_with_nul(&self) -> &[u8] {
182 self.as_bytes_with_nul()
183 }
184}
185
186impl CStrArray<0> {
187 pub const fn empty() -> Self {
197 Self { data: [], nul: Nul }
198 }
199}
200
201impl<const N: usize> Debug for CStrArray<N> {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 <CStr as Debug>::fmt(self.as_c_str(), f)
204 }
205}
206
207impl<const N: usize> Deref for CStrArray<N> {
208 type Target = CStr;
209
210 fn deref(&self) -> &Self::Target {
211 self.as_c_str()
212 }
213}
214
215impl<const N: usize> AsRef<CStr> for CStrArray<N> {
216 fn as_ref(&self) -> &CStr {
217 self.as_c_str()
218 }
219}
220
221impl<const N: usize> TryFrom<&CStr> for CStrArray<N> {
222 type Error = CStrLenError<N>;
223
224 fn try_from(val: &CStr) -> Result<Self, Self::Error> {
225 Self::new(val)
226 }
227}
228
229#[cfg(feature = "alloc")]
230mod alloc {
231 use super::*;
232 use crate::*;
233
234 impl<const N: usize> TryFrom<Box<CStr>> for CStrArray<N> {
235 type Error = CStrLenError<N>;
236
237 fn try_from(val: Box<CStr>) -> Result<Self, Self::Error> {
238 Self::new(&val)
239 }
240 }
241
242 impl<const N: usize> TryFrom<Rc<CStr>> for CStrArray<N> {
243 type Error = CStrLenError<N>;
244
245 fn try_from(val: Rc<CStr>) -> Result<Self, Self::Error> {
246 Self::new(&val)
247 }
248 }
249
250 impl<const N: usize> TryFrom<Arc<CStr>> for CStrArray<N> {
251 type Error = CStrLenError<N>;
252
253 fn try_from(val: Arc<CStr>) -> Result<Self, Self::Error> {
254 Self::new(&val)
255 }
256 }
257
258 impl<const N: usize> TryFrom<CString> for CStrArray<N> {
259 type Error = CStrLenError<N>;
260
261 fn try_from(val: CString) -> Result<Self, Self::Error> {
262 Self::new(&val)
263 }
264 }
265
266 impl<const N: usize> PartialEq<CString> for CStrArray<N> {
267 fn eq(&self, other: &CString) -> bool {
268 self.as_c_str() == other.as_c_str()
269 }
270 }
271
272 impl<const N: usize> PartialEq<CStrArray<N>> for CString {
273 fn eq(&self, other: &CStrArray<N>) -> bool {
274 self.as_c_str() == other.as_c_str()
275 }
276 }
277}
278
279impl<const N: usize> PartialEq<CStr> for CStrArray<N> {
280 fn eq(&self, other: &CStr) -> bool {
281 self.as_c_str() == other
282 }
283}
284
285impl<const N: usize> PartialEq<CStrArray<N>> for CStr {
286 fn eq(&self, other: &CStrArray<N>) -> bool {
287 self == other.as_c_str()
288 }
289}
290
291impl<const N: usize> PartialEq<&CStr> for CStrArray<N> {
292 fn eq(&self, other: &&CStr) -> bool {
293 self.as_c_str() == *other
294 }
295}
296
297impl<const N: usize> PartialEq<CStrArray<N>> for &CStr {
298 fn eq(&self, other: &CStrArray<N>) -> bool {
299 *self == other.as_c_str()
300 }
301}
302
303pub struct CStrArrayBytes<T>(pub T);
305
306pub const fn build_cstr<const N: usize>(bytes: &[u8]) -> CStrArray<N> {
308 let as_array: &[u8; N] = if bytes.len() == N {
309 unsafe { &*bytes.as_ptr().cast() }
311 } else {
312 CStrLenError::<N> {
313 src_len: bytes.len(),
314 }
315 .const_panic()
316 };
317 match CStrArray::from_bytes_without_nul(as_array) {
318 Ok(x) => x,
319 Err(e) => e.const_panic(),
320 }
321}
322
323impl<'a, const N: usize> CStrArrayBytes<&'a [u8; N]> {
324 pub const fn get(self) -> &'a [u8] {
326 self.0
327 }
328}
329
330impl<'a> CStrArrayBytes<&'a [u8]> {
331 pub const fn get(self) -> &'a [u8] {
333 self.0
334 }
335}
336
337impl<'a> CStrArrayBytes<&'a str> {
338 pub const fn get(self) -> &'a [u8] {
340 self.0.as_bytes()
341 }
342}
343
344impl<'a> CStrArrayBytes<&'a CStr> {
345 pub const fn get(self) -> &'a [u8] {
347 unsafe { slice::from_raw_parts(self.0.as_ptr().cast(), count_bytes(self.0)) }
350 }
351}
352
353#[macro_export]
434macro_rules! cstr_array {
435 (@impl item
437 ($([$attr:meta])*)
438 ($($item_kind:tt)*)
439 $name:ident = $val:expr; $($rest:tt)*
440 ) => {
441 $(#[$attr])* $($item_kind)* $name: $crate::CStrArray<{ $crate::__internal::CStrArrayBytes($val).get().len() }> =
442 $crate::__internal::build_cstr($crate::__internal::CStrArrayBytes($val).get());
443 $crate::cstr_array!($($rest)*)
444 };
445 ($(#[$attr:meta])* static mut $($rest:tt)*) => {
446 $crate::cstr_array!(@impl item ($([$attr])*) (static mut) $($rest)*);
447 };
448 ($(#[$attr:meta])* static $($rest:tt)*) => {
449 $crate::cstr_array!(@impl item ($([$attr])*) (static) $($rest)*);
450 };
451 ($(#[$attr:meta])* const $($rest:tt)*) => {
452 $crate::cstr_array!(@impl item ($([$attr])*) (const) $($rest)*);
453 };
454 ($val:expr) => {{
455 const VAL: &[u8] = $crate::__internal::CStrArrayBytes($val).get();
456 const ARRAY: $crate::CStrArray<{ VAL.len() }> = $crate::__internal::build_cstr(VAL);
457 ARRAY
458 }};
459 () => {};
460}
461
462#[cfg(test)]
463macro_rules! cstr {
464 ($x:literal) => {
465 match core::ffi::CStr::from_bytes_with_nul(concat!($x, "\0").as_bytes()) {
466 Ok(x) => x,
467 Err(_) => panic!(),
468 }
469 };
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475 use ::alloc::format;
476
477 #[test]
478 fn test_new() {
479 let cstr = cstr!("hello");
480 let cstr_array = CStrArray::<5>::new(cstr).unwrap();
481 assert_eq!(cstr_array.as_c_str(), cstr);
482 assert_eq!(cstr_array.len(), 5);
483 assert!(!cstr_array.is_empty());
484
485 let cstr_long = cstr!("hellos");
486 let err = CStrArray::<5>::new(cstr_long).unwrap_err();
487 assert_eq!(err.src_len, 6);
488
489 let cstr_short = cstr!("hell");
490 let err = CStrArray::<5>::new(cstr_short).unwrap_err();
491 assert_eq!(err.src_len, 4);
492 }
493
494 #[test]
495 fn test_from_bytes_without_nul() {
496 let bytes = b"hello";
497 let cstr_array = CStrArray::from_bytes_without_nul(bytes).unwrap();
498 assert_eq!(cstr_array.as_bytes(), bytes);
499
500 let bytes_with_nul = b"he\0llo";
501 let err = CStrArray::from_bytes_without_nul(bytes_with_nul).unwrap_err();
502 assert_eq!(err.position, 2);
503 }
504
505 #[test]
506 fn test_empty() {
507 let empty = CStrArray::<0>::empty();
508 assert!(empty.is_empty());
509 assert_eq!(empty.len(), 0);
510 assert_eq!(empty.as_c_str(), <&CStr>::default());
511 }
512
513 #[test]
514 fn test_debug() {
515 let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
516 let s = format!("{:?}", cstr_array);
517 assert_eq!(s, r#""hello""#);
518 }
519
520 #[test]
521 fn test_as_bytes_with_nul() {
522 let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
523 assert_eq!(cstr_array.as_bytes_with_nul(), b"hello\0");
524 }
525
526 #[test]
527 fn test_into_bytes() {
528 let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
529 assert_eq!(cstr_array.into_bytes(), *b"hello");
530 }
531
532 #[test]
533 fn test_try_from_cstr() {
534 let cstr = cstr!("hello");
535 let cstr_array = CStrArray::<5>::try_from(cstr).unwrap();
536 assert_eq!(cstr_array.as_c_str(), cstr);
537
538 let cstr_long = cstr!("hellos");
539 let err = CStrArray::<5>::try_from(cstr_long).unwrap_err();
540 assert_eq!(err.src_len, 6);
541 }
542
543 #[test]
544 fn test_partial_eq() {
545 let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
546 let cstr = cstr!("hello");
547 assert_eq!(&cstr_array, cstr);
548 assert_eq!(cstr, &cstr_array);
549 assert_eq!(&cstr_array, &cstr);
550 assert_eq!(&cstr, &cstr_array);
551 }
552
553 #[test]
554 fn test_macro() {
555 let cstr_array = cstr_array!(cstr!("hello"));
556 assert_eq!(cstr_array.len(), 5);
557 assert_eq!(cstr_array, cstr!("hello"));
558
559 let str_array = cstr_array!("hello");
560 assert_eq!(str_array.len(), 5);
561 assert_eq!(str_array, cstr!("hello"));
562
563 let bytes_array = cstr_array!(b"hello");
564 assert_eq!(bytes_array.len(), 5);
565 assert_eq!(bytes_array, cstr!("hello"));
566 }
567
568 #[test]
569 #[deny(non_upper_case_globals)]
570 fn test_macro_items() {
571 cstr_array! {
572 static STATIC = cstr!("hello");
573 const CONST = "world";
574 }
575 assert_eq!(STATIC.len(), 5);
576 assert_eq!(STATIC, cstr!("hello"));
577 assert_eq!(CONST.len(), 5);
578 assert_eq!(CONST, cstr!("world"));
579 }
580}
581
582#[cfg(all(test, feature = "alloc"))]
583mod alloc_tests {
584 use super::*;
585 use ::alloc::{boxed::Box, ffi::CString, rc::Rc, sync::Arc};
586
587 #[test]
588 fn test_try_from_alloc() {
589 let cstr = cstr!("hello");
590 let cstring = CString::new("hello").unwrap();
591
592 let from_box = CStrArray::<5>::try_from(Box::from(cstr)).unwrap();
593 assert_eq!(from_box, cstr);
594
595 let from_rc = CStrArray::<5>::try_from(Rc::from(cstr)).unwrap();
596 assert_eq!(from_rc, cstr);
597
598 let from_arc = CStrArray::<5>::try_from(Arc::from(cstr)).unwrap();
599 assert_eq!(from_arc, cstr);
600
601 let from_cstring = CStrArray::<5>::try_from(cstring.clone()).unwrap();
602 assert_eq!(from_cstring, cstr);
603 }
604
605 #[test]
606 fn test_partial_eq_alloc() {
607 let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
608 let cstring = CString::new("hello").unwrap();
609 assert_eq!(cstr_array, *cstring);
610 assert_eq!(*cstring, cstr_array);
611 }
612}