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}