null_terminated_str/
borrowed.rs1use std::{
2 cmp::Ordering,
3 ffi::CStr,
4 fmt,
5 ops::Deref,
6 os::raw::c_char,
7 str::{from_utf8, from_utf8_unchecked, Utf8Error},
8};
9
10use super::NullTerminatedString;
11
12const fn report_err() -> &'static NullTerminatedStr {
13 const EMPTY_ARR: [&NullTerminatedStr; 0] = [];
14 #[allow(unconditional_panic)]
15 EMPTY_ARR[0]
16}
17
18#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(transparent)]
20pub struct NullTerminatedStr(CStr);
21
22impl NullTerminatedStr {
23 pub const fn as_c_str(&self) -> &CStr {
24 &self.0
25 }
26
27 pub const fn as_ptr(&self) -> *const c_char {
28 self.as_c_str().as_ptr()
29 }
30
31 pub const unsafe fn from_cstr_unchecked(cstr: &CStr) -> &Self {
36 &*(cstr as *const CStr as *const Self)
39 }
40
41 pub fn from_cstr(cstr: &CStr) -> Result<&Self, Utf8Error> {
42 from_utf8(cstr.to_bytes())?;
43 Ok(unsafe { Self::from_cstr_unchecked(cstr) })
44 }
45
46 const fn is_null_terminated(s: &str) -> bool {
49 let bytes = s.as_bytes();
50
51 if bytes.is_empty() {
52 return false;
53 }
54
55 let mut i = 0;
56 let n = bytes.len() - 1;
57
58 if bytes[n] != b'\0' {
60 return false;
61 }
62
63 while i < n {
65 if bytes[i] == b'\0' {
66 return false;
67 }
68 i += 1;
69 }
70
71 true
72 }
73
74 pub const fn from_const_str(s: &str) -> &Self {
80 if let Some(null_str) = Self::try_from_str(s) {
81 null_str
82 } else {
83 report_err()
84 }
85 }
86
87 pub const fn try_from_str(s: &str) -> Option<&Self> {
122 if Self::is_null_terminated(s) {
123 Some(unsafe {
124 Self::from_cstr_unchecked(CStr::from_bytes_with_nul_unchecked(s.as_bytes()))
125 })
126 } else {
127 None
128 }
129 }
130}
131
132impl Deref for NullTerminatedStr {
133 type Target = str;
134
135 fn deref(&self) -> &Self::Target {
136 unsafe { from_utf8_unchecked(self.0.to_bytes()) }
137 }
138}
139
140impl PartialEq<str> for NullTerminatedStr {
141 fn eq(&self, other: &str) -> bool {
142 self.deref().eq(other)
143 }
144}
145
146impl PartialEq<NullTerminatedStr> for str {
147 fn eq(&self, other: &NullTerminatedStr) -> bool {
148 self.eq(other.deref())
149 }
150}
151
152impl PartialOrd<str> for NullTerminatedStr {
153 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
154 self.deref().partial_cmp(other)
155 }
156}
157
158impl PartialOrd<NullTerminatedStr> for str {
159 fn partial_cmp(&self, other: &NullTerminatedStr) -> Option<Ordering> {
160 self.partial_cmp(other.deref())
161 }
162}
163
164impl fmt::Display for NullTerminatedStr {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 self.deref().fmt(f)
167 }
168}
169
170impl<'a> TryFrom<&'a CStr> for &'a NullTerminatedStr {
171 type Error = Utf8Error;
172
173 fn try_from(cstr: &'a CStr) -> Result<Self, Self::Error> {
174 NullTerminatedStr::from_cstr(cstr)
175 }
176}
177
178impl ToOwned for NullTerminatedStr {
179 type Owned = NullTerminatedString;
180
181 fn to_owned(&self) -> Self::Owned {
182 let cstring = self.as_c_str().to_owned();
183 unsafe { NullTerminatedString::from_cstring_unchecked(cstring) }
184 }
185}
186
187impl<'a> From<&'a NullTerminatedStr> for &'a CStr {
188 fn from(null_str: &'a NullTerminatedStr) -> Self {
189 null_str.as_c_str()
190 }
191}
192
193impl AsRef<NullTerminatedStr> for NullTerminatedStr {
194 fn as_ref(&self) -> &NullTerminatedStr {
195 self
196 }
197}
198
199impl AsRef<str> for NullTerminatedStr {
200 fn as_ref(&self) -> &str {
201 self.deref()
202 }
203}
204
205impl AsRef<CStr> for NullTerminatedStr {
206 fn as_ref(&self) -> &CStr {
207 self.as_c_str()
208 }
209}