konst/ffi/
cstr.rs

1//! Const equivalents of [`CStr`] methods
2
3#[cfg(test)]
4mod err_tests;
5
6use crate::slice::slice_up_to;
7
8use core::{ffi::CStr, fmt};
9
10////////////////////////////////////////////////////////////////////////////////
11
12/// Error returned by [`from_bytes_until_nul`] when the input slice either
13/// does not terminate with nul.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct FromBytesUntilNulError(());
16
17impl FromBytesUntilNulError {
18    /// Const equivalent of `FromBytesUntilNulError::clone`
19    pub const fn copy(&self) -> Self {
20        Self(())
21    }
22
23    /// Panics with this type's error message
24    #[track_caller]
25    pub const fn panic(&self) -> ! {
26        panic!("{}", self.err_msg())
27    }
28    const fn err_msg(&self) -> &str {
29        "data provided does not contain a nul"
30    }
31}
32
33impl fmt::Display for FromBytesUntilNulError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.write_str(self.err_msg())
36    }
37}
38
39////////////////////////////////////////////////////////////////////////////////
40
41/// Error returned by [`from_bytes_with_nul`] when the input slice either
42/// does not terminate with nul, or contains inner nul bytes.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct FromBytesWithNulError {
45    kind: HuntNulError,
46}
47
48impl FromBytesWithNulError {
49    /// Const equivalent of `FromBytesWithNulError::clone`
50    pub const fn copy(&self) -> Self {
51        Self { kind: self.kind }
52    }
53
54    const fn err_msg(&self) -> (&str, Option<usize>) {
55        match self.kind {
56            HuntNulError::InternalNul(pos) => {
57                ("input bytes contain an internal nul byte at: ", Some(pos))
58            }
59            HuntNulError::NotNulTerminated => ("input bytes don't terminate with nul", None),
60        }
61    }
62
63    /// Panics with this type's error message
64    #[track_caller]
65    pub const fn panic(&self) -> ! {
66        use const_panic::{concat_panic, FmtArg, PanicVal};
67
68        let (msg, num) = self.err_msg();
69
70        concat_panic(&[&[
71            PanicVal::write_str(msg),
72            match num {
73                Some(x) => PanicVal::from_usize(x, FmtArg::DEBUG),
74                None => PanicVal::EMPTY,
75            },
76        ]])
77    }
78}
79
80impl fmt::Display for FromBytesWithNulError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let (msg, num) = self.err_msg();
83        f.write_str(msg)?;
84        if let Some(num) = num {
85            write!(f, "{num}")?;
86        }
87        Ok(())
88    }
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, Eq)]
92enum HuntNulError {
93    InternalNul(usize),
94    NotNulTerminated,
95}
96
97////////////////////////////////////////////////////////////////////////////////
98
99struct CStrAndLen<'a> {
100    cstr: &'a CStr,
101    length_with_nul: usize,
102}
103
104const fn from_bytes_until_nul_inner(
105    bytes: &[u8],
106) -> Result<CStrAndLen<'_>, FromBytesUntilNulError> {
107    crate::for_range! {i in 0..bytes.len() =>
108        if bytes[i] == 0 {
109            let sub = slice_up_to(bytes, i + 1);
110            unsafe {
111                return Ok(CStrAndLen{
112                    cstr: CStr::from_bytes_with_nul_unchecked(sub),
113                    length_with_nul: i + 1,
114                });
115            }
116        }
117    }
118
119    Err(FromBytesUntilNulError(()))
120}
121
122/// Converts a byte slice which contains any amount of nul bytes into a `&CStr`.
123/// Const equivalent of [`CStr::from_bytes_until_nul`]
124///
125/// # Const stabilization
126///
127/// The equivalent std function was const-stabilized in Rust 1.69.0.
128///
129/// # Example
130///
131/// ```rust
132/// use konst::{ffi::cstr, unwrap_ctx};
133///
134/// use std::ffi::CStr;
135///
136///
137/// const CS: &CStr = unwrap_ctx!(cstr::from_bytes_until_nul(b"hello\0world"));
138///
139/// assert_eq!(CS.to_str().unwrap(), "hello");
140///
141/// ```
142///
143pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError> {
144    match from_bytes_until_nul_inner(bytes) {
145        Ok(CStrAndLen { cstr, .. }) => Ok(cstr),
146        Err(e) => Err(e),
147    }
148}
149
150/// Converts a nul-terminated byte slice into a `&CStr`.
151/// Const equivalent of [`CStr::from_bytes_with_nul`]
152///
153/// # Const stabilization
154///
155/// The equivalent std function was const-stabilized in Rust 1.72.0.
156///
157/// # Example
158///
159/// ```rust
160/// use konst::{ffi::cstr, unwrap_ctx};
161///
162/// use std::ffi::CStr;
163///
164///
165/// const CS: &CStr = unwrap_ctx!(cstr::from_bytes_with_nul(b"foo bar\0"));
166///
167/// assert_eq!(CS.to_str().unwrap(), "foo bar");
168///
169/// ```
170///
171pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&CStr, FromBytesWithNulError> {
172    const fn make_not_null_term_err<T>() -> Result<T, FromBytesWithNulError> {
173        Err(FromBytesWithNulError {
174            kind: HuntNulError::NotNulTerminated,
175        })
176    }
177
178    match from_bytes_until_nul_inner(bytes) {
179        Ok(CStrAndLen {
180            cstr,
181            length_with_nul,
182        }) if length_with_nul == bytes.len() => Ok(cstr),
183        Ok(_) if bytes[bytes.len() - 1] != 0 => make_not_null_term_err(),
184        Err(_) => make_not_null_term_err(),
185        Ok(CStrAndLen {
186            length_with_nul, ..
187        }) => Err(FromBytesWithNulError {
188            kind: HuntNulError::InternalNul(length_with_nul - 1),
189        }),
190    }
191}
192
193/// Converts this CStr to a byte slice, including the nul terminator.
194/// Const equivalent of [`CStr::to_bytes_with_nul`]
195///
196/// # Performance
197///
198/// This function takes linear time to run, proportional to the length of `this`.
199///
200/// # Const stabilization
201///
202/// The equivalent std function was const-stabilized in Rust 1.72.0.
203///
204/// # Example
205///
206/// ```rust
207/// use konst::{ffi::cstr, unwrap_ctx};
208///
209/// use std::ffi::CStr;
210///
211///
212/// const CS: &CStr = unwrap_ctx!(cstr::from_bytes_with_nul(b"example\0"));
213///
214/// const BYTES: &[u8] = cstr::to_bytes_with_nul(CS);
215///
216/// assert_eq!(BYTES, b"example\0");
217///
218/// ```
219pub const fn to_bytes_with_nul(this: &CStr) -> &[u8] {
220    let start = this.as_ptr().cast::<u8>();
221    let mut i = 0;
222
223    unsafe {
224        while *start.add(i) != 0 {
225            i += 1;
226        }
227
228        core::slice::from_raw_parts(start, i + 1)
229    }
230}
231
232/// Converts this CStr to a byte slice, excluding the nul terminator.
233/// Const equivalent of [`CStr::to_bytes`]
234///
235/// # Performance
236///
237/// This function takes linear time to run, proportional to the length of `this`.
238///
239/// # Const stabilization
240///
241/// The equivalent std function was const-stabilized in Rust 1.72.0.
242///
243/// # Example
244///
245/// ```rust
246/// use konst::{ffi::cstr, unwrap_ctx};
247///
248/// use std::ffi::CStr;
249///
250///
251/// const CS: &CStr = unwrap_ctx!(cstr::from_bytes_with_nul(b"hmm...\0"));
252///
253/// const BYTES: &[u8] = cstr::to_bytes(CS);
254///
255/// assert_eq!(BYTES, b"hmm...");
256///
257/// ```
258pub const fn to_bytes(this: &CStr) -> &[u8] {
259    match to_bytes_with_nul(this) {
260        [rem @ .., 0] => rem,
261        _ => unreachable!(),
262    }
263}
264
265/// Converts this CStr to a string slice, excluding the nul terminator.
266/// Const equivalent of [`CStr::to_str`]
267///
268/// # Performance
269///
270/// This function takes linear time to run, proportional to the length of `this`.
271///
272/// # Const stabilization
273///
274/// The equivalent std function was const-stabilized in Rust 1.72.0.
275///
276/// # Example
277///
278/// ```rust
279/// use konst::{ffi::cstr, unwrap_ctx};
280///
281/// use std::ffi::CStr;
282///
283///
284/// const CS: &CStr = unwrap_ctx!(cstr::from_bytes_with_nul(b"of beads\0"));
285///
286/// const STRING: &str = unwrap_ctx!(cstr::to_str(CS));
287///
288/// assert_eq!(STRING, "of beads");
289///
290/// ```
291pub const fn to_str(this: &CStr) -> Result<&str, crate::string::Utf8Error> {
292    crate::string::from_utf8(to_bytes(this))
293}