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}