1#[cfg(feature = "std")]
2use std::{ffi::OsStr, path::Path};
3
4use core::{
5 cmp,
6 ffi::{CStr, FromBytesWithNulError},
7 fmt,
8 ops::{Deref, Index},
9 slice::SliceIndex,
10 str::{self, Utf8Error},
11};
12
13#[repr(transparent)]
18#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct CStr8 {
20 raw: str,
21}
22
23impl Deref for CStr8 {
24 type Target = str;
25
26 fn deref(&self) -> &str {
27 self.as_str()
28 }
29}
30
31impl Default for &'_ CStr8 {
32 fn default() -> Self {
33 cstr8!("")
34 }
35}
36
37impl fmt::Debug for CStr8 {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 self.as_str().fmt(f)
40 }
41}
42
43impl fmt::Display for CStr8 {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 self.as_str().fmt(f)
46 }
47}
48
49impl AsRef<str> for CStr8 {
50 fn as_ref(&self) -> &str {
51 self.as_str()
52 }
53}
54
55impl AsRef<CStr> for CStr8 {
56 fn as_ref(&self) -> &CStr {
57 self.as_c_str()
58 }
59}
60
61impl AsRef<[u8]> for CStr8 {
62 fn as_ref(&self) -> &[u8] {
63 self.as_bytes()
64 }
65}
66
67impl PartialEq<str> for CStr8 {
68 fn eq(&self, other: &str) -> bool {
69 self.as_str() == other
70 }
71}
72
73impl PartialEq<CStr8> for str {
74 fn eq(&self, other: &CStr8) -> bool {
75 self == other.as_str()
76 }
77}
78
79impl PartialOrd<str> for CStr8 {
80 fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
81 self.as_str().partial_cmp(other)
82 }
83}
84
85impl PartialOrd<CStr8> for str {
86 fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
87 self.partial_cmp(other.as_str())
88 }
89}
90
91impl PartialEq<CStr> for CStr8 {
92 fn eq(&self, other: &CStr) -> bool {
93 self.as_c_str() == other
94 }
95}
96
97impl PartialEq<CStr8> for CStr {
98 fn eq(&self, other: &CStr8) -> bool {
99 self == other.as_c_str()
100 }
101}
102
103impl PartialOrd<CStr> for CStr8 {
104 fn partial_cmp(&self, other: &CStr) -> Option<cmp::Ordering> {
105 self.as_c_str().partial_cmp(other)
106 }
107}
108
109impl PartialOrd<CStr8> for CStr {
110 fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
111 self.partial_cmp(other.as_c_str())
112 }
113}
114
115impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
116 type Error = CStr8Error;
117
118 fn try_from(s: &'a CStr) -> Result<&'a CStr8, CStr8Error> {
119 CStr8::from_utf8_with_nul(s.to_bytes_with_nul())
120 }
121}
122
123#[cfg(feature = "alloc")]
124mod alloc_impls {
125 use {
126 crate::{CStr8, CString8},
127 alloc::{
128 borrow::{Cow, ToOwned},
129 boxed::Box,
130 ffi::CString,
131 rc::Rc,
132 string::String,
133 sync::Arc,
134 },
135 core::ffi::CStr,
136 };
137
138 impl From<&CStr8> for Arc<CStr8> {
142 fn from(s: &CStr8) -> Arc<CStr8> {
143 let arc = Arc::<[u8]>::from(s.as_bytes_with_nul());
144 unsafe { Arc::from_raw(Arc::into_raw(arc) as *const CStr8) }
146 }
147 }
148
149 impl From<&CStr8> for Arc<CStr> {
150 fn from(s: &CStr8) -> Arc<CStr> {
151 s.as_c_str().into()
152 }
153 }
154
155 impl From<&CStr8> for Arc<str> {
156 fn from(s: &CStr8) -> Arc<str> {
157 s.as_str().into()
158 }
159 }
160
161 impl From<&CStr8> for Box<CStr8> {
162 fn from(s: &CStr8) -> Box<CStr8> {
163 let boxed = Box::<[u8]>::from(s.as_bytes_with_nul());
164 unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr8) }
166 }
167 }
168
169 impl From<&CStr8> for Box<CStr> {
170 fn from(s: &CStr8) -> Box<CStr> {
171 s.as_c_str().into()
172 }
173 }
174
175 impl From<&CStr8> for Box<str> {
176 fn from(s: &CStr8) -> Box<str> {
177 s.as_str().into()
178 }
179 }
180
181 impl<'a> From<&'a CStr8> for Cow<'a, CStr8> {
182 fn from(s: &'a CStr8) -> Cow<'a, CStr8> {
183 Cow::Borrowed(s)
184 }
185 }
186
187 impl<'a> From<&'a CStr8> for Cow<'a, CStr> {
188 fn from(s: &'a CStr8) -> Cow<'a, CStr> {
189 s.as_c_str().into()
190 }
191 }
192
193 impl<'a> From<&'a CStr8> for Cow<'a, str> {
194 fn from(s: &'a CStr8) -> Cow<'a, str> {
195 s.as_str().into()
196 }
197 }
198
199 impl From<&CStr8> for Rc<CStr8> {
200 fn from(s: &CStr8) -> Rc<CStr8> {
201 let rc = Rc::<[u8]>::from(s.as_bytes_with_nul());
202 unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr8) }
204 }
205 }
206
207 impl From<&CStr8> for Rc<CStr> {
208 fn from(s: &CStr8) -> Rc<CStr> {
209 s.as_c_str().into()
210 }
211 }
212
213 impl From<&CStr8> for Rc<str> {
214 fn from(s: &CStr8) -> Rc<str> {
215 s.as_str().into()
216 }
217 }
218
219 impl From<&CStr8> for CString8 {
220 fn from(s: &CStr8) -> CString8 {
221 s.to_owned()
222 }
223 }
224
225 impl From<&CStr8> for CString {
226 fn from(s: &CStr8) -> CString {
227 s.as_c_str().into()
228 }
229 }
230
231 impl From<&CStr8> for String {
232 fn from(s: &CStr8) -> String {
233 s.as_str().into()
234 }
235 }
236
237 impl From<Cow<'_, CStr8>> for Box<CStr8> {
238 fn from(s: Cow<'_, CStr8>) -> Box<CStr8> {
239 match s {
240 Cow::Borrowed(s) => Box::from(s),
241 Cow::Owned(s) => Box::from(s),
242 }
243 }
244 }
245
246 impl PartialEq<String> for CStr8 {
247 fn eq(&self, other: &String) -> bool {
248 self.as_str() == other
249 }
250 }
251
252 impl PartialEq<CStr8> for String {
253 fn eq(&self, other: &CStr8) -> bool {
254 self == other.as_str()
255 }
256 }
257
258 impl ToOwned for CStr8 {
259 type Owned = CString8;
260
261 fn to_owned(&self) -> CString8 {
262 unsafe { CString8::from_vec_unchecked(self.as_bytes_with_nul().to_owned()) }
264 }
265
266 }
272}
273
274#[cfg(feature = "std")]
275mod std_impls {
276 use {
277 crate::CStr8,
278 core::cmp,
279 std::{
280 ffi::{OsStr, OsString},
281 path::Path,
282 },
283 };
284
285 impl AsRef<OsStr> for CStr8 {
286 fn as_ref(&self) -> &OsStr {
287 self.as_str().as_ref()
288 }
289 }
290
291 impl AsRef<Path> for CStr8 {
292 fn as_ref(&self) -> &Path {
293 self.as_str().as_ref()
294 }
295 }
296
297 impl PartialEq<OsStr> for CStr8 {
298 fn eq(&self, other: &OsStr) -> bool {
299 self.as_str() == other
300 }
301 }
302
303 impl PartialEq<OsString> for &'_ CStr8 {
304 fn eq(&self, other: &OsString) -> bool {
305 self.as_str() == other
306 }
307 }
308
309 impl PartialEq<OsString> for CStr8 {
310 fn eq(&self, other: &OsString) -> bool {
311 self.as_str() == other
312 }
313 }
314
315 impl PartialEq<CStr8> for OsStr {
316 fn eq(&self, other: &CStr8) -> bool {
317 self == other.as_str()
318 }
319 }
320
321 impl PartialEq<CStr8> for OsString {
322 fn eq(&self, other: &CStr8) -> bool {
323 self == other.as_str()
324 }
325 }
326
327 impl PartialOrd<CStr8> for OsStr {
328 fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
329 self.partial_cmp(other.as_str())
330 }
331 }
332
333 impl PartialOrd<CStr8> for OsString {
334 fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
335 self.partial_cmp(other.as_str())
336 }
337 }
338}
339
340impl<I> Index<I> for CStr8
341where
342 I: SliceIndex<str>,
343{
344 type Output = I::Output;
345
346 fn index(&self, index: I) -> &Self::Output {
347 &self.as_str()[index]
348 }
349}
350
351impl CStr8 {
353 pub const fn as_str(&self) -> &str {
358 match self.raw.as_bytes() {
359 [rest @ .., _nul] => unsafe { str::from_utf8_unchecked(rest) },
360 [] => unreachable!(),
361 }
362 }
363
364 pub const fn as_c_str(&self) -> &CStr {
369 unsafe { CStr::from_bytes_with_nul_unchecked(self.raw.as_bytes()) }
370 }
371
372 pub const fn as_bytes(&self) -> &[u8] {
377 self.as_str().as_bytes()
378 }
379
380 pub const fn as_bytes_with_nul(&self) -> &[u8] {
385 self.raw.as_bytes()
386 }
387
388 #[cfg(feature = "std")]
393 pub fn as_os_str(&self) -> &OsStr {
394 self.as_ref()
395 }
396
397 #[cfg(feature = "std")]
402 pub fn as_path(&self) -> &Path {
403 self.as_ref()
404 }
405
406 pub const fn as_ptr(&self) -> *const u8 {
412 self.as_bytes_with_nul().as_ptr()
413 }
414}
415
416impl CStr8 {
418 pub fn from_utf8_with_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
454 let _ = str::from_utf8(v)?;
455 let _ = CStr::from_bytes_with_nul(v)?;
456 Ok(unsafe { CStr8::from_utf8_with_nul_unchecked(v) })
457 }
458
459 pub fn from_utf8_until_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
487 let v = CStr::from_bytes_until_nul(v)
488 .map(CStr::to_bytes_with_nul)
489 .unwrap_or_default();
490 Self::from_utf8_with_nul(v)
491 }
492
493 pub const unsafe fn from_utf8_with_nul_unchecked(v: &[u8]) -> &CStr8 {
500 &*(v as *const [u8] as *const CStr8)
501 }
502
503 pub unsafe fn from_ptr<'a>(ptr: *const u8) -> &'a CStr8 {
510 CStr8::from_utf8_with_nul_unchecked(CStr::from_ptr(ptr.cast()).to_bytes_with_nul())
511 }
512}
513
514#[derive(Debug)]
518pub enum CStr8Error {
519 InvalidUtf8(Utf8Error),
521 NulError(FromBytesWithNulError),
523}
524
525#[cfg(feature = "std")]
526impl std::error::Error for CStr8Error {
527 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
528 match self {
529 CStr8Error::InvalidUtf8(source) => Some(source),
530 CStr8Error::NulError(source) => Some(source),
531 }
532 }
533}
534
535impl fmt::Display for CStr8Error {
536 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
537 match self {
538 CStr8Error::InvalidUtf8(_) => f.write_str("invalid UTF-8"),
539 CStr8Error::NulError(_) => f.write_str("invalid nul terminator"),
540 }
541 }
542}
543
544impl From<Utf8Error> for CStr8Error {
545 fn from(source: Utf8Error) -> Self {
546 CStr8Error::InvalidUtf8(source)
547 }
548}
549
550impl From<FromBytesWithNulError> for CStr8Error {
551 fn from(source: FromBytesWithNulError) -> Self {
552 CStr8Error::NulError(source)
553 }
554}