rustix/
cstr.rs

1/// A macro for [`CStr`] literals.
2///
3/// This can make passing string literals to rustix APIs more efficient, since
4/// most underlying system calls with string arguments expect NUL-terminated
5/// strings, and passing strings to rustix as `CStr`s means that rustix doesn't
6/// need to copy them into a separate buffer to NUL-terminate them.
7///
8/// In Rust ≥ 1.77, users can use [C-string literals] instead of this macro.
9///
10/// [`CStr`]: crate::ffi::CStr
11/// [C-string literals]: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html#c-string-literals
12///
13/// # Examples
14///
15/// ```
16/// # #[cfg(feature = "fs")]
17/// # fn main() -> rustix::io::Result<()> {
18/// use rustix::cstr;
19/// use rustix::fs::{statat, AtFlags, CWD};
20///
21/// let metadata = statat(CWD, cstr!("Cargo.toml"), AtFlags::empty())?;
22/// # Ok(())
23/// # }
24/// # #[cfg(not(feature = "fs"))]
25/// # fn main() {}
26/// ```
27#[allow(unused_macros)]
28#[macro_export]
29macro_rules! cstr {
30    ($str:literal) => {{
31        // Check for NUL manually, to ensure safety.
32        //
33        // In release builds, with strings that don't contain NULs, this
34        // constant-folds away.
35        //
36        // We don't use std's `CStr::from_bytes_with_nul`; as of this writing,
37        // that function isn't defined as `#[inline]` in std and doesn't
38        // constant-fold away.
39        ::core::assert!(
40            !::core::iter::Iterator::any(&mut ::core::primitive::str::bytes($str), |b| b == b'\0'),
41            "cstr argument contains embedded NUL bytes",
42        );
43
44        #[allow(unsafe_code, unused_unsafe)]
45        {
46            // Now that we know the string doesn't have embedded NULs, we can
47            // call `from_bytes_with_nul_unchecked`, which as of this writing
48            // is defined as `#[inline]` and completely optimizes away.
49            //
50            // SAFETY: We have manually checked that the string does not
51            // contain embedded NULs above, and we append or own NUL terminator
52            // here.
53            unsafe {
54                $crate::ffi::CStr::from_bytes_with_nul_unchecked(
55                    ::core::concat!($str, "\0").as_bytes(),
56                )
57            }
58        }
59    }};
60}
61
62#[cfg(test)]
63mod tests {
64    #[allow(unused_imports)]
65    use super::*;
66
67    #[test]
68    fn test_cstr() {
69        use crate::ffi::CString;
70        use alloc::borrow::ToOwned as _;
71        assert_eq!(cstr!(""), &*CString::new("").unwrap());
72        assert_eq!(cstr!("").to_owned(), CString::new("").unwrap());
73        assert_eq!(cstr!("hello"), &*CString::new("hello").unwrap());
74        assert_eq!(cstr!("hello").to_owned(), CString::new("hello").unwrap());
75    }
76
77    #[test]
78    #[should_panic]
79    fn test_invalid_cstr() {
80        let _ = cstr!("hello\0world");
81    }
82
83    #[test]
84    #[should_panic]
85    fn test_invalid_empty_cstr() {
86        let _ = cstr!("\0");
87    }
88
89    #[no_implicit_prelude]
90    mod hygiene {
91        #[allow(unused_macros)]
92        #[test]
93        fn macro_hygiene() {
94            macro_rules! assert {
95                ($($tt:tt)*) => {
96                    ::core::panic!("cstr! called the wrong assert! macro");
97                };
98            }
99            macro_rules! concat {
100                ($($tt:tt)*) => {{
101                    let v: &str = ::core::panic!("cstr! called the wrong concat! macro");
102                    v
103                }};
104            }
105
106            let _ = cstr!("foo");
107        }
108    }
109}