abi_stable/sabi_types/nul_str.rs
1//! A nul-terminated string,which is just a pointer to the string data,
2//! it doesn't know the length of the string.
3
4#[cfg(test)]
5mod tests;
6
7use crate::std_types::RStr;
8
9use const_panic::{concat_assert, concat_panic};
10
11use std::{
12 cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
13 fmt::{self, Debug, Display},
14 marker::PhantomData,
15 ptr::NonNull,
16};
17
18/// A utf8 nul-terminated immutable borrowed string.
19///
20/// For the purpose of passing `NulStr`s to C,
21/// this has the same ABI as a `std::ptr::NonNull<u8>`,
22/// and an `Option<NulStr<'_>>` has the same ABI as `*const u8`.
23///
24/// # Safety
25///
26/// `NulStr` has these safety requirement:
27/// - the string must be valid to read for the `'a` lifetime
28/// - the string must be utf8 encoded
29/// - the string must be nul terminated
30/// - the string must not be mutated while this is alive
31/// (the same semantics as `&` references)
32///
33/// # Example
34///
35/// ### Passing to extern function
36///
37/// You can pass `NulStr` to C functions expecting a nul-terminated string.
38///
39/// ```rust
40/// use abi_stable::sabi_types::NulStr;
41///
42/// extern "C" {
43/// // the signature in the C side is `uint64_t add_digits(const char*)`
44/// fn add_digits(_: NulStr<'_>) -> u64;
45/// }
46/// # #[export_name = "add_digits"]
47/// # pub extern "C" fn add_digits___(str: NulStr<'_>) -> u64 {
48/// # str.to_str().bytes()
49/// # .filter_map(|x|{
50/// # match x {
51/// # b'0'..=b'9' => Some(u64::from(x - b'0')),
52/// # _ => None,
53/// # }
54/// # })
55/// # .sum()
56/// # }
57///
58/// # fn main() {
59/// const FOO: NulStr<'_> = NulStr::from_str("1.2.3\0");
60/// const BAR: NulStr<'_> = NulStr::from_str("12|34\0");
61/// const QUX: NulStr<'_> = NulStr::from_str("123_abcd_45\0");
62///
63/// assert_eq!(unsafe { add_digits(FOO) }, 6);
64/// assert_eq!(unsafe { add_digits(BAR) }, 10);
65/// assert_eq!(unsafe { add_digits(QUX) }, 15);
66/// # }
67/// ```
68#[repr(transparent)]
69#[derive(Copy, Clone, StableAbi)]
70pub struct NulStr<'a> {
71 ptr: NonNull<u8>,
72 _marker: PhantomData<&'a u8>,
73}
74
75unsafe impl Sync for NulStr<'_> {}
76unsafe impl Send for NulStr<'_> {}
77
78impl NulStr<'static> {
79 /// An empty string.
80 pub const EMPTY: Self = NulStr::from_str("\0");
81}
82
83impl<'a> NulStr<'a> {
84 /// Constructs an `NulStr` from a string slice.
85 ///
86 /// # Correctness
87 ///
88 /// If the string contains interior nuls,
89 /// the first nul will be considered the string terminator.
90 ///
91 /// # Panics
92 ///
93 /// This panics when the string does not end with `'\0'`.
94 ///
95 /// # Example
96 ///
97 /// ```rust
98 /// use abi_stable::sabi_types::NulStr;
99 ///
100 /// const FOO: NulStr<'_> = NulStr::from_str("foo\0");
101 /// // `NulStr`s can be compared with `str`s
102 /// assert_eq!(FOO, "foo");
103 ///
104 /// const BAR: NulStr<'_> = NulStr::from_str("bar\0");
105 /// assert_eq!(BAR, "bar");
106 ///
107 /// const HEWWO: NulStr<'_> = NulStr::from_str("Hello, world!\0");
108 /// assert_eq!(HEWWO, "Hello, world!");
109 ///
110 /// const TRUNCATED: NulStr<'_> = NulStr::from_str("baz\0world!\0");
111 /// assert_eq!(TRUNCATED, "baz");
112 ///
113 /// ```
114 pub const fn from_str(str: &'a str) -> Self {
115 let this = Self {
116 ptr: crate::utils::ref_as_nonnull(str).cast::<u8>(),
117 _marker: PhantomData,
118 };
119
120 let last_byte = str.as_bytes()[str.len() - 1] as usize;
121 concat_assert! {
122 last_byte == 0,
123 "expected a nul terminator, found:",
124 last_byte,
125 };
126 this
127 }
128
129 /// Constructs an NulStr from a string slice.
130 ///
131 /// # Errors
132 ///
133 /// This returns a [`NulStrError::NoNulTerminator`] when the string does not end
134 /// with `'\0'`.
135 ///
136 /// This returns a [`NulStrError::InnerNul`] when the string contains a
137 /// `'\0'` before the `'\0'` terminator.
138 ///
139 /// # Example
140 ///
141 /// ```rust
142 /// use abi_stable::sabi_types::{NulStr, NulStrError};
143 ///
144 /// // `NulStr`s can be compared with `str`s
145 /// assert_eq!(NulStr::try_from_str("hello\0").unwrap(), "hello");
146 ///
147 /// assert_eq!(
148 /// NulStr::try_from_str("hello\0world\0"),
149 /// Err(NulStrError::InnerNul { pos: 5 }),
150 /// );
151 ///
152 /// ```
153 ///
154 /// [`NulStrError::InnerNul`]: enum.NulStrError.html#variant.InnerNul
155 /// [`NulStrError::NoNulTerminator`]: enum.NulStrError.html#variant.NoNulTerminator
156 pub const fn try_from_str(string: &'a str) -> Result<Self, NulStrError> {
157 let mut i = 0;
158 let mut bytes = string.as_bytes();
159
160 bytes = match bytes {
161 [rem @ .., 0] => rem,
162 _ => return Err(NulStrError::NoNulTerminator),
163 };
164
165 while let [b, ref rem @ ..] = *bytes {
166 if b == 0 {
167 return Err(NulStrError::InnerNul { pos: i });
168 }
169 i += 1;
170 bytes = rem;
171 }
172
173 unsafe { Ok(NulStr::from_ptr(string.as_ptr())) }
174 }
175
176 #[doc(hidden)]
177 #[track_caller]
178 pub const fn __try_from_str_unwrapping(s: &'a str) -> Self {
179 match Self::try_from_str(s) {
180 Ok(x) => x,
181 Err(NulStrError::InnerNul { pos }) => {
182 concat_panic!("encountered inner nul byte at position: ", pos)
183 }
184 Err(NulStrError::NoNulTerminator) => concat_panic!("found no nul-terminator"),
185 }
186 }
187
188 /// Constructs an NulStr from a pointer.
189 ///
190 /// # Safety
191 ///
192 /// [The same as the type-level safety docs](#safety)
193 ///
194 /// # Correctness
195 ///
196 /// If the string contains interior nuls,
197 /// the first nul will be considered the string terminator.
198 ///
199 /// # Example
200 ///
201 /// ```rust
202 /// use abi_stable::sabi_types::NulStr;
203 ///
204 /// const FOO: NulStr<'_> = unsafe { NulStr::from_ptr("foo\0".as_ptr()) };
205 /// assert_eq!(FOO, "foo");
206 ///
207 /// const BAR: NulStr<'_> = unsafe { NulStr::from_ptr("bar\0".as_ptr()) };
208 /// assert_eq!(BAR, "bar");
209 ///
210 /// const HEWWO: NulStr<'_> = unsafe { NulStr::from_ptr("Hello, world!\0".as_ptr()) };
211 /// assert_eq!(HEWWO, "Hello, world!");
212 ///
213 /// const TRUNCATED: NulStr<'_> = unsafe { NulStr::from_ptr("baz\0world!\0".as_ptr()) };
214 /// assert_eq!(TRUNCATED, "baz");
215 ///
216 /// ```
217 pub const unsafe fn from_ptr(ptr: *const u8) -> Self {
218 Self {
219 ptr: unsafe { NonNull::new_unchecked(ptr as *mut u8) },
220 _marker: PhantomData,
221 }
222 }
223
224 /// Gets a pointer to the start of this nul-terminated string.
225 ///
226 /// # Example
227 ///
228 /// ```rust
229 /// use abi_stable::sabi_types::NulStr;
230 ///
231 /// let foo_str = "foo\0";
232 /// let foo = NulStr::from_str(foo_str);
233 /// assert_eq!(foo.as_ptr(), foo_str.as_ptr());
234 ///
235 /// ```
236 pub const fn as_ptr(self) -> *const u8 {
237 self.ptr.as_ptr()
238 }
239
240 /// Converts this `NulStr<'a>` to a `&'a str`,including the nul byte.
241 ///
242 /// # Performance
243 ///
244 /// This conversion requires traversing through the entire string to
245 /// find the nul byte.
246 ///
247 /// # Example
248 ///
249 /// ```rust
250 /// use abi_stable::sabi_types::NulStr;
251 ///
252 /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
253 /// let foo: &str = FOO.to_str_with_nul();
254 /// assert_eq!(&foo[..3], "foo");
255 /// assert_eq!(&foo[4..], "bar\0");
256 ///
257 /// ```
258 pub fn to_str_with_nul(&self) -> &'a str {
259 unsafe {
260 let bytes = std::ffi::CStr::from_ptr(self.ptr.as_ptr() as *const _).to_bytes_with_nul();
261 std::str::from_utf8_unchecked(bytes)
262 }
263 }
264
265 /// Computes the length of the string, NOT including the nul terminator.
266 #[cfg(feature = "rust_1_64")]
267 const fn compute_length(self) -> usize {
268 let start: *const u8 = self.ptr.as_ptr();
269 let mut ptr = start;
270 let mut len = 0;
271 unsafe {
272 while *ptr != 0 {
273 ptr = ptr.offset(1);
274 len += 1;
275 }
276 len
277 }
278 }
279
280 /// Converts this `NulStr<'a>` to a `&'a str`,including the nul byte.
281 ///
282 /// # Performance
283 ///
284 /// To make this function const-callable,
285 /// this uses a potentially less efficient approach than
286 /// [`to_str_with_nul`](Self::to_str_with_nul).
287 ///
288 /// This conversion requires traversing through the entire string to
289 /// find the nul byte.
290 ///
291 /// # Example
292 ///
293 /// ```rust
294 /// use abi_stable::sabi_types::NulStr;
295 ///
296 /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
297 /// const FOO_S: &str = FOO.const_to_str_with_nul();
298 /// assert_eq!(&FOO_S[..3], "foo");
299 /// assert_eq!(&FOO_S[4..], "bar\0");
300 ///
301 /// ```
302 #[cfg(feature = "rust_1_64")]
303 #[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
304 pub const fn const_to_str_with_nul(&self) -> &'a str {
305 unsafe {
306 let len = self.compute_length();
307 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.as_ptr(), len + 1))
308 }
309 }
310
311 /// Converts this `NulStr<'a>` to a `RStr<'a>`,including the nul byte.
312 ///
313 /// # Performance
314 ///
315 /// This conversion requires traversing through the entire string to
316 /// find the nul byte.
317 ///
318 /// # Example
319 ///
320 /// ```rust
321 /// use abi_stable::sabi_types::NulStr;
322 /// use abi_stable::std_types::RStr;
323 ///
324 /// const BAZ: NulStr<'_> = NulStr::from_str("baz qux\0");
325 /// let baz: RStr<'_> = BAZ.to_rstr_with_nul();
326 /// assert_eq!(&baz[..3], "baz");
327 /// assert_eq!(&baz[4..], "qux\0");
328 ///
329 /// ```
330 pub fn to_rstr_with_nul(&self) -> RStr<'a> {
331 self.to_str_with_nul().into()
332 }
333
334 /// Converts this `NulStr<'a>` to a `&'a str`,not including the nul byte.
335 ///
336 /// # Performance
337 ///
338 /// This conversion requires traversing through the entire string to
339 /// find the nul byte.
340 ///
341 /// # Example
342 ///
343 /// ```rust
344 /// use abi_stable::sabi_types::NulStr;
345 ///
346 /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
347 /// let foo: &str = FOO.to_str();
348 /// assert_eq!(&foo[..3], "foo");
349 /// assert_eq!(&foo[4..], "bar");
350 ///
351 /// ```
352 pub fn to_str(self) -> &'a str {
353 unsafe {
354 let bytes = std::ffi::CStr::from_ptr(self.ptr.as_ptr() as *const _).to_bytes();
355 std::str::from_utf8_unchecked(bytes)
356 }
357 }
358
359 /// Converts this `NulStr<'a>` to a `&'a str`,not including the nul byte.
360 ///
361 /// # Performance
362 ///
363 /// To make this function const-callable,
364 /// this uses a potentially less efficient approach than [`to_str`](Self::to_str).
365 ///
366 /// This conversion requires traversing through the entire string to
367 /// find the nul byte.
368 ///
369 /// # Example
370 ///
371 /// ```rust
372 /// use abi_stable::sabi_types::NulStr;
373 ///
374 /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
375 /// const FOO_S: &str = FOO.const_to_str();
376 /// assert_eq!(&FOO_S[..3], "foo");
377 /// assert_eq!(&FOO_S[4..], "bar");
378 ///
379 /// ```
380 #[cfg(feature = "rust_1_64")]
381 #[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
382 pub const fn const_to_str(self) -> &'a str {
383 unsafe {
384 let len = self.compute_length();
385 std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.as_ptr(), len))
386 }
387 }
388
389 /// Converts this `NulStr<'a>` to a `RStr<'a>`,not including the nul byte.
390 ///
391 /// # Performance
392 ///
393 /// This conversion requires traversing through the entire string to
394 /// find the nul byte.
395 ///
396 /// # Example
397 ///
398 /// ```rust
399 /// use abi_stable::sabi_types::NulStr;
400 /// use abi_stable::std_types::RStr;
401 ///
402 /// const BAZ: NulStr<'_> = NulStr::from_str("baz qux\0");
403 /// let baz: RStr<'_> = BAZ.to_rstr();
404 /// assert_eq!(&baz[..3], "baz");
405 /// assert_eq!(&baz[4..], "qux");
406 ///
407 /// ```
408 pub fn to_rstr(self) -> RStr<'a> {
409 self.to_str().into()
410 }
411}
412
413impl<'a> PartialEq<NulStr<'a>> for &str {
414 fn eq(&self, other: &NulStr<'a>) -> bool {
415 self.as_ptr() == other.as_ptr() || *self == other.to_str()
416 }
417}
418
419impl<'a> PartialEq<&str> for NulStr<'a> {
420 fn eq(&self, other: &&str) -> bool {
421 self.as_ptr() == other.as_ptr() || self.to_str() == *other
422 }
423}
424
425impl<'a> PartialEq<NulStr<'a>> for str {
426 fn eq(&self, other: &NulStr<'a>) -> bool {
427 self.as_ptr() == other.as_ptr() || self == other.to_str()
428 }
429}
430
431impl<'a> PartialEq<str> for NulStr<'a> {
432 fn eq(&self, other: &str) -> bool {
433 self.as_ptr() == other.as_ptr() || self.to_str() == other
434 }
435}
436
437impl<'a> PartialEq for NulStr<'a> {
438 fn eq(&self, other: &Self) -> bool {
439 self.ptr == other.ptr || self.to_str() == other.to_str()
440 }
441}
442
443impl<'a> Eq for NulStr<'a> {}
444
445impl<'a> PartialOrd for NulStr<'a> {
446 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
447 Some(self.cmp(other))
448 }
449}
450
451impl<'a> Ord for NulStr<'a> {
452 fn cmp(&self, other: &Self) -> Ordering {
453 if self.ptr == other.ptr {
454 Ordering::Equal
455 } else {
456 self.to_str().cmp(other.to_str())
457 }
458 }
459}
460
461impl Default for NulStr<'_> {
462 fn default() -> Self {
463 NulStr::EMPTY
464 }
465}
466
467impl Display for NulStr<'_> {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 Display::fmt(self.to_str(), f)
470 }
471}
472
473impl Debug for NulStr<'_> {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 Debug::fmt(self.to_str(), f)
476 }
477}
478
479/// Error from trying to convert a `&str` to a [`NulStr`]
480///
481/// [`NulStr`]: ./struct.NulStr.html
482#[derive(Debug, PartialEq, Eq, Clone)]
483#[non_exhaustive]
484pub enum NulStrError {
485 /// When the string has a `'\0'` before the end.
486 InnerNul {
487 /// the position of the first '\0' character.
488 pos: usize,
489 },
490 /// When the string doesn't end with `'\0'`
491 NoNulTerminator,
492}
493
494impl Display for NulStrError {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 match *self {
497 Self::InnerNul { pos } => {
498 write!(f, "there is an internal nul at the {} byte offset", pos)
499 }
500 Self::NoNulTerminator => f.write_str("there is no nul terminator in the string"),
501 }
502 }
503}
504
505impl std::error::Error for NulStrError {}