null_terminated_str/
owned.rs

1use std::{
2    borrow::Borrow,
3    error::Error,
4    ffi::{CStr, CString},
5    fmt,
6    ops::Deref,
7    str::Utf8Error,
8};
9
10use super::NullTerminatedStr;
11
12#[derive(Clone, Debug)]
13pub struct NullStringFromUtf8Error {
14    cstring: CString,
15    utf8_err: Utf8Error,
16}
17
18impl NullStringFromUtf8Error {
19    pub fn into_inner(self) -> (CString, Utf8Error) {
20        (self.cstring, self.utf8_err)
21    }
22}
23
24impl fmt::Display for NullStringFromUtf8Error {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        self.utf8_err.fmt(f)
27    }
28}
29
30impl Error for NullStringFromUtf8Error {
31    fn source(&self) -> Option<&(dyn Error + 'static)> {
32        Some(&self.utf8_err)
33    }
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[repr(transparent)]
38pub struct NullTerminatedString(CString);
39
40impl NullTerminatedString {
41    /// # Safety
42    ///
43    /// `cstring` (excluding the null byte) must be valid utf-8 str.
44    pub const unsafe fn from_cstring_unchecked(cstring: CString) -> Self {
45        Self(cstring)
46    }
47
48    pub fn from_cstring(cstring: CString) -> Result<Self, NullStringFromUtf8Error> {
49        if let Err(utf8_err) = NullTerminatedStr::from_cstr(&cstring) {
50            Err(NullStringFromUtf8Error { cstring, utf8_err })
51        } else {
52            Ok(Self(cstring))
53        }
54    }
55
56    pub fn from_cstring_lossy(cstring: CString) -> Self {
57        Self::from_cstring(cstring).unwrap_or_else(|NullStringFromUtf8Error { cstring, .. }| {
58            // bytes without null byte
59            let bytes = cstring.into_bytes();
60
61            // This would replace any invalid utf-8 sequence with
62            // `std::char::REPLACEMENT_CHARACTER`, which does not
63            // contain null byte.
64            let string = String::from_utf8_lossy(&bytes).into_owned();
65
66            // Convert it back into bytes
67            let bytes = string.into_bytes();
68
69            // from_vec_unchecked appends the trailing '\0'
70            //
71            // Safety:
72            //
73            // The string cannot have any null byte.
74            Self(unsafe { CString::from_vec_unchecked(bytes) })
75        })
76    }
77}
78
79impl From<&str> for NullTerminatedString {
80    fn from(s: &str) -> Self {
81        let buf = s.bytes().filter(|byte| *byte != b'\0').collect::<Vec<_>>();
82        // from_vec_unchecked appends the trailing '\0'
83        //
84        // Safety:
85        // All '\0' is removed before passing in.
86        Self(unsafe { CString::from_vec_unchecked(buf) })
87    }
88}
89
90impl From<String> for NullTerminatedString {
91    fn from(s: String) -> Self {
92        let mut buf = s.into_bytes();
93        buf.retain(|byte| *byte != b'\0');
94        // from_vec_unchecked appends the trailing '\0'
95        //
96        // Safety:
97        // All '\0' is removed before passing in.
98        Self(unsafe { CString::from_vec_unchecked(buf) })
99    }
100}
101
102impl From<NullTerminatedString> for String {
103    fn from(s: NullTerminatedString) -> String {
104        // bytes without trailing null byte
105        let bytes = s.0.into_bytes();
106        // Safety:
107        //
108        // NullTerminatedString contains valid utf-8 string,
109        // excluding the trailing null byte.
110        unsafe { String::from_utf8_unchecked(bytes) }
111    }
112}
113
114impl From<NullTerminatedString> for CString {
115    fn from(s: NullTerminatedString) -> CString {
116        s.0
117    }
118}
119
120impl Deref for NullTerminatedString {
121    type Target = NullTerminatedStr;
122
123    fn deref(&self) -> &Self::Target {
124        unsafe { NullTerminatedStr::from_cstr_unchecked(self.0.as_c_str()) }
125    }
126}
127
128impl fmt::Display for NullTerminatedString {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        self.deref().fmt(f)
131    }
132}
133
134impl Borrow<NullTerminatedStr> for NullTerminatedString {
135    fn borrow(&self) -> &NullTerminatedStr {
136        self.deref()
137    }
138}
139
140impl AsRef<NullTerminatedStr> for NullTerminatedString {
141    fn as_ref(&self) -> &NullTerminatedStr {
142        self.deref()
143    }
144}
145
146impl AsRef<str> for NullTerminatedString {
147    fn as_ref(&self) -> &str {
148        self.deref().as_ref()
149    }
150}
151
152impl AsRef<CStr> for NullTerminatedString {
153    fn as_ref(&self) -> &CStr {
154        self.deref().as_c_str()
155    }
156}