safer_ffi/
char_p.rs

1#![cfg_attr(rustfmt, rustfmt::skip)]
2//! `char *`-compatible strings (slim pointers), for easier use from within C.
3//
4//! They thus do not support inner nulls, nor string appending.
5
6use_prelude!();
7use ::core::slice;
8
9ReprC! {
10    #[repr(transparent)]
11    #[derive(Clone, Copy)]
12    /// A `#[repr(c)]` null-terminated UTF-8 encoded string, for compatibility
13    /// with both the C `char *` API and Rust's `str`.
14    ///
15    /// This is a **borrowed** version, _i.e._, with the semantics of
16    /// `&'lt CStr` / `&'lt str`, but for it being a _slim_ pointer.
17    pub
18    struct char_p_ref['lt,] (
19        ptr::NonNullRef<c_char>,
20        PhantomCovariantLifetime<'lt>,
21    );
22}
23
24const NUL: u8 = b'\0';
25
26impl char_p_ref<'static> {
27    pub
28    const EMPTY: Self = unsafe {
29        Self::from_ptr_unchecked(ptr::NonNull::new_unchecked({
30            const IT: &u8 = &NUL;
31            IT as *const u8 as *mut u8
32        }))
33    };
34}
35
36impl<'lt> char_p_ref<'lt> {
37    pub
38    const
39    unsafe
40    fn from_ptr_unchecked (ptr: ptr::NonNull<u8>)
41      -> Self
42    {
43        Self(
44            ptr::NonNullRef(ptr.cast()),
45            PhantomCovariantLifetime::<'static>(PhantomData),
46        )
47    }
48}
49
50impl fmt::Debug
51    for char_p_ref<'_>
52{
53    fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
54      -> fmt::Result
55    {
56        fmt::Debug::fmt(self.to_str(), fmt)
57    }
58}
59
60impl fmt::Display
61    for char_p_ref<'_>
62{
63    fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
64      -> fmt::Result
65    {
66        fmt::Display::fmt(self.to_str(), fmt)
67    }
68}
69
70unsafe // Safety: proof delegated to `where` bound.
71impl Send for char_p_ref<'_>
72where
73    for<'lt> &'lt [u8] : Send,
74{}
75
76unsafe // Safety: proof delegated to `where` bound.
77impl Sync for char_p_ref<'_>
78where
79    for<'lt> &'lt [u8] : Sync,
80{}
81
82unsafe // Safety: no non-`unsafe` API.
83impl Send for char_p_raw
84{}
85
86unsafe // Safety: no non-`unsafe` API.
87impl Sync for char_p_raw
88{}
89
90#[derive(Debug)]
91pub
92struct InvalidNulTerminator<Payload> (
93    pub Payload,
94);
95
96impl<T> fmt::Display
97    for InvalidNulTerminator<T>
98{
99    fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
100      -> fmt::Result
101    {
102        fmt::Display::fmt(
103            "Null byte not at the expected terminating position",
104            fmt,
105        )
106    }
107}
108
109impl<'lt> TryFrom<&'lt str>
110    for char_p_ref<'lt>
111{
112    type Error = InvalidNulTerminator<()>;
113
114    fn try_from (s: &'lt str)
115      -> Result<
116            char_p_ref<'lt>,
117            InvalidNulTerminator<()>,
118        >
119    {
120        Ok(if let Some(len_minus_one) = s.len().checked_sub(1) {
121            unsafe {
122                if s.bytes().position(|b| b == NUL) != Some(len_minus_one) {
123                    return Err(InvalidNulTerminator(()));
124                }
125                Self::from_ptr_unchecked(
126                    ptr::NonNull::new(s.as_ptr() as _).unwrap()
127                )
128            }
129        } else {
130            char_p_ref::EMPTY
131        })
132    }
133}
134
135cfg_std! {
136    impl<T : fmt::Debug> ::std::error::Error
137        for InvalidNulTerminator<T>
138    {}
139
140    /// # Panic
141    ///
142    /// Panics if the `CStr` is not valid UTF-8.
143    impl<'lt> From<&'lt ::std::ffi::CStr>
144        for char_p_ref<'lt>
145    {
146        #[inline]
147        fn from (s: &'lt ::std::ffi::CStr)
148          -> char_p_ref<'lt>
149        {
150            unsafe {
151                let _assert_valid_utf8 =
152                    ::core::str::from_utf8(s.to_bytes())
153                        .unwrap()
154                ;
155                Self::from_ptr_unchecked(
156                    ptr::NonNull::new(s.as_ptr() as _)
157                        .unwrap()
158                )
159            }
160        }
161    }
162}
163
164impl<'lt> char_p_ref<'lt> {
165    #[inline]
166    pub
167    fn bytes (self: char_p_ref<'lt>)
168      -> impl Iterator<Item = ::core::num::NonZeroU8> + 'lt
169    {
170        ::core::iter::from_fn({
171            let mut ptr = self.0.as_ptr().cast::<u8>();
172            move || {
173                let ret = ::core::num::NonZeroU8::new(unsafe { ptr.read() });
174                if ret.is_some() {
175                    unsafe {
176                        ptr = ptr.add(1);
177                    }
178                }
179                ret
180            }
181        })
182    }
183
184    #[inline]
185    pub
186    fn to_nonzero_bytes (self: char_p_ref<'lt>)
187      -> &'lt [::core::num::NonZeroU8]
188    {
189        unsafe {
190            slice::from_raw_parts(
191                self.0.as_ptr().cast(),
192                self.bytes().count(),
193            )
194        }
195    }
196
197    #[inline]
198    pub
199    fn to_bytes (self: char_p_ref<'lt>)
200      -> &'lt [u8]
201    {
202        unsafe {
203            slice::from_raw_parts(
204                self.0.as_ptr().cast(),
205                self.bytes().count(),
206            )
207        }
208    }
209
210    #[inline]
211    pub
212    fn to_bytes_with_null (self: char_p_ref<'lt>)
213      -> &'lt [u8]
214    {
215        unsafe {
216            slice::from_raw_parts(
217                self.0.as_ptr().cast(),
218                self.bytes().count() + 1,
219            )
220        }
221    }
222
223    #[inline]
224    pub
225    fn to_str (self: char_p_ref<'lt>)
226      -> &'lt str
227    {
228        unsafe {
229            ::core::str::from_utf8_unchecked(self.to_bytes())
230        }
231    }
232
233    #[inline]
234    pub
235    fn to_str_with_null (self: char_p_ref<'lt>)
236      -> &'lt str
237    {
238        unsafe {
239            ::core::str::from_utf8_unchecked(self.to_bytes_with_null())
240        }
241    }
242
243    cfg_alloc! {
244        #[inline]
245        /// Poor-man's specialization of `ToString::to_string()`
246        pub
247        fn to_string(&self)
248          -> rust::String
249        {
250            self.to_str().to_owned()
251        }
252
253        #[inline]
254        pub
255        fn to_owned (self: char_p_ref<'lt>)
256          -> char_p_boxed
257        {
258            char_p::new(self.to_str())
259        }
260    }
261}
262
263impl<'lt> Eq for char_p_ref<'lt> {}
264impl<'lt> PartialEq for char_p_ref<'lt> {
265    #[inline]
266    fn eq (self: &'_ Self, other: &'_ Self)
267      -> bool
268    {
269        *self.to_str() == *other.to_str()
270    }
271}
272
273ReprC! {
274    #[repr(transparent)]
275    #[allow(missing_copy_implementations)]
276    /// Same as [`char_p_ref`], but without any lifetime attached whatsoever.
277    ///
278    /// It is only intended to be used as the parameter of a **callback** that
279    /// locally borrows it, due to limitations of the [`ReprC`][
280    /// `trait@crate::layout::ReprC`] design _w.r.t._ higher-rank trait bounds.
281    pub
282    struct char_p_raw (
283        ptr::NonNullRef<c_char>,
284    );
285}
286
287#[deny(unsafe_op_in_unsafe_fn)]
288impl char_p_raw {
289    /// # Safety
290    ///
291    ///   - For the duration of the `'borrow`, the pointer must point to the
292    ///     beginning of a valid and immutable null-terminated slice of
293    ///     `c_char`s.
294    pub
295    unsafe
296    fn as_ref<'borrow> (self: &'borrow Self)
297      -> char_p_ref<'borrow>
298    {
299        unsafe {
300            // # Safety
301            //
302            //   - Same layout,
303            //
304            //   - Caller guarantees the validity of the borrow.
305            ::core::mem::transmute(self.0)
306        }
307    }
308}
309
310impl<'lt> From<char_p_ref<'lt>>
311    for char_p_raw
312{
313    #[inline]
314    fn from (it: char_p_ref<'lt>)
315      -> char_p_raw
316    {
317        unsafe {
318            mem::transmute(it)
319        }
320    }
321}
322
323impl fmt::Debug
324    for char_p_raw
325{
326    fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
327      -> fmt::Result
328    {
329        fmt .debug_tuple("char_p_raw")
330            .field(&self.0)
331            .finish()
332    }
333}
334
335cfg_alloc! {
336    /// Constructs a new `char_p::Box` off a stringy input.
337    ///
338    /// ## Panics
339    ///
340    /// Panics if the stringy input:
341    ///   - contains an interior (non-last) `NUL` byte (for `str`-y types);
342    ///   - is not UTF-8 (for `CStr`-y types).
343    #[inline]
344    pub
345    fn new<Str> (s: Str)
346      -> char_p_boxed
347    where
348        char_p_boxed : hidden::__<Str>,
349    {
350        MyFrom::my_from(s)
351    }
352
353    ReprC! {
354        #[repr(transparent)]
355        /// A `#[repr(c)]` null-terminated UTF-8 encoded string, for compatibility
356        /// with both the `char *` C API and Rust `str`.
357        ///
358        /// This is an **owned** / heap-allocated version, much like `Box<str>`
359        /// / `Box<CStr>` but for it being a _slim_ pointer.
360        pub
361        struct char_p_boxed (
362            ptr::NonNullOwned<c_char>,
363        );
364    }
365
366    unsafe // Safety: inherited from `Box<[u8]>`.
367    impl Send
368        for char_p_boxed
369    where
370        rust::Box<[u8]> : Send,
371    {}
372
373    unsafe // Safety: inherited from `Box<[u8]>`.
374    impl Sync
375        for char_p_boxed
376    where
377        rust::Box<[u8]> : Sync,
378    {}
379
380    impl fmt::Debug
381        for char_p_boxed
382    {
383        fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
384          -> fmt::Result
385        {
386            fmt::Debug::fmt(&self.as_ref(), fmt)
387        }
388    }
389    impl fmt::Display
390        for char_p_boxed
391    {
392        fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>)
393          -> fmt::Result
394        {
395            fmt::Display::fmt(&self.as_ref(), fmt)
396        }
397    }
398
399    impl char_p_boxed {
400        #[inline]
401        pub
402        const
403        unsafe
404        fn from_ptr_unchecked (ptr: ptr::NonNull<u8>)
405          -> char_p_boxed
406        {
407            Self(
408                ptr::NonNullOwned(ptr.cast(), PhantomData),
409            )
410        }
411
412        #[inline]
413        pub
414        fn as_ref (self: &'_ char_p_boxed)
415          -> char_p_ref<'_>
416        {
417            unsafe {
418                mem::transmute(self.0.as_ref())
419            }
420        }
421    }
422
423    use hidden::__ as MyFrom; mod hidden {
424        pub
425        trait __<Orig> { fn my_from (it: Orig) -> Self; }
426    }
427    macro_rules! derive_MyFrom_from {(
428        $(
429            $(@for[$($generics:tt)*])?
430            $T:ty => $Intermediate:ty
431        ),* $(,)?
432    ) => ($(
433        impl$(<$($generics)*>)? MyFrom<$T>
434            for char_p_boxed
435        {
436            #[inline]
437            fn my_from (it: $T)
438              -> char_p_boxed
439            {
440                // Note, the TryFrom for CString is unfallible so the bad error
441                // message is not seen
442                <Self as TryFrom<$Intermediate>>::try_from(it.into())
443                    .unwrap_or_else(|s| panic!(concat!(
444                        "Error, the string `{:?}` contains an inner nul byte",
445                        " and can thus not be converted to a C string without ",
446                        "truncating it.",
447                    ), s))
448            }
449        }
450    )*)}
451    derive_MyFrom_from! {
452        @for['lt] &'lt str => rust::String,
453        @for['lt] str::Ref<'lt> => rust::String,
454        rust::String => rust::String,
455        repr_c::String => rust::String,
456    }
457    cfg_std! {
458        derive_MyFrom_from! {
459            @for['lt] &'lt ::std::ffi::CStr => ::std::ffi::CString,
460            ::std::ffi::CString => ::std::ffi::CString,
461        }
462    }
463
464    /// We use a `static` rather than a `const` for the empty string case
465    /// as its address serves as a sentinel value for a fake-boxed string.
466    /// (Otherwise empty `char_p_boxed` would need to allocate to hold the
467    /// `NUL` terminator).
468    static EMPTY_SENTINEL: u8 = NUL;
469
470    impl TryFrom<rust::String> for char_p_boxed {
471        type Error = InvalidNulTerminator<rust::String>;
472
473        fn try_from (s: rust::String)
474          -> Result<
475                char_p_boxed,
476                InvalidNulTerminator<rust::String>,
477            >
478        {
479            Ok(if let ("" | "\0") = &*s {
480                unsafe {
481                    Self::from_ptr_unchecked(ptr::NonNull::new_unchecked(
482                        (&EMPTY_SENTINEL) as *const _ as *mut _
483                    ))
484                }
485            } else {
486                let len_minus_one = s.len() - 1;
487                if s.as_bytes()[.. len_minus_one].contains(&NUL) {
488                    return Err(InvalidNulTerminator(s));
489                }
490                let mut s = s;
491                if s.as_bytes()[len_minus_one] != NUL {
492                    s.reserve_exact(1);
493                    s.push(NUL as _);
494                }
495                let s: rust::Box<[u8]> = s.into_boxed_str().into();
496                unsafe {
497                    Self::from_ptr_unchecked(
498                        ptr::NonNull::new(rust::Box::into_raw(s) as *mut u8)
499                            .unwrap()
500                    )
501                }
502            })
503        }
504    }
505
506    impl Drop for char_p_boxed {
507        fn drop (self: &'_ mut char_p_boxed)
508        {
509            unsafe {
510                if ptr::eq(self.0.as_mut_ptr().cast(), &EMPTY_SENTINEL) {
511                    return;
512                }
513                let num_bytes = self.to_bytes_with_null().len();
514                drop::<rust::Box<[u8]>>(
515                    rust::Box::from_raw(slice::from_raw_parts_mut(
516                        self.0.as_mut_ptr().cast(),
517                        num_bytes,
518                    ))
519                );
520            }
521        }
522    }
523
524    impl char_p_boxed {
525        #[inline]
526        pub
527        fn bytes<'lt> (self: &'lt char_p_boxed)
528          -> impl Iterator<Item = ::core::num::NonZeroU8> + 'lt
529        {
530            self.as_ref().bytes()
531        }
532
533        #[inline]
534        pub
535        fn to_nonzero_bytes (self: &'_ char_p_boxed)
536          -> &'_ [::core::num::NonZeroU8]
537        {
538            self.as_ref().to_nonzero_bytes()
539        }
540
541        #[inline]
542        pub
543        fn to_bytes (self: &'_ char_p_boxed)
544          -> &'_ [u8]
545        {
546            self.as_ref().to_bytes()
547        }
548
549        #[inline]
550        pub
551        fn to_bytes_with_null (self: &'_ char_p_boxed)
552          -> &'_ [u8]
553        {
554            self.as_ref().to_bytes_with_null()
555        }
556
557        #[inline]
558        pub
559        fn to_str (self: &'_ char_p_boxed)
560          -> &'_ str
561        {
562            self.as_ref().to_str()
563        }
564
565        #[inline]
566        pub
567        fn to_str_with_null (self: &'_ char_p_boxed)
568          -> &'_ str
569        {
570            self.as_ref().to_str_with_null()
571        }
572
573        pub
574        fn into_vec (mut self: char_p_boxed)
575          -> rust::Vec<u8>
576        {
577            if ptr::eq(self.0.as_mut_ptr().cast(), &EMPTY_SENTINEL) {
578                return vec![];
579            }
580            let num_bytes = self.to_bytes_with_null().len();
581            let ptr = mem::ManuallyDrop::new(self).0.as_mut_ptr();
582            let boxed_bytes = unsafe {
583                rust::Box::from_raw(slice::from_raw_parts_mut(
584                    ptr.cast(),
585                    num_bytes,
586                ))
587            };
588            let mut vec = rust::Vec::from(boxed_bytes);
589            vec.pop();
590            vec
591        }
592
593        #[inline]
594        pub
595        fn into_string (self: char_p_boxed)
596          -> rust::String
597        {
598            unsafe {
599                rust::String::from_utf8_unchecked(
600                    self.into_vec()
601                )
602            }
603        }
604
605        #[inline]
606        pub
607        fn to_owned (self: &'_ char_p_boxed)
608          -> char_p_boxed
609        {
610            self.as_ref().to_owned()
611        }
612    }
613
614    impl Eq
615        for char_p_boxed
616    {}
617    impl PartialEq
618        for char_p_boxed {
619        #[inline]
620        fn eq (self: &'_ Self, other: &'_ Self)
621          -> bool
622        {
623            self.as_ref() == other.as_ref()
624        }
625    }
626
627    impl Clone
628        for char_p_boxed
629    {
630        #[inline]
631        fn clone (self: &'_ Self)
632          -> Self
633        {
634            self.to_owned()
635        }
636    }
637}
638
639cfg_std! {
640    /// # Panic
641    ///
642    /// Panics if the `CString` is not valid UTF-8.
643    impl From<::std::ffi::CString> for char_p_boxed {
644        fn from (s: ::std::ffi::CString)
645          -> char_p_boxed
646        {
647            let _assert_valid_utf8 =
648                ::core::str::from_utf8(s.as_bytes())
649                    .unwrap()
650            ;
651            let s: rust::Box<[u8]> =
652                s   .into_bytes_with_nul()
653                    .into_boxed_slice()
654            ;
655            unsafe {
656                Self::from_ptr_unchecked(
657                    ptr::NonNull::new(rust::Box::leak(s).as_mut_ptr())
658                        .unwrap()
659                )
660            }
661        }
662    }
663}