zombiezen_const_cstr/lib.rs
1// Copyright (c) 2020 const-cstr developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! Create static C-compatible strings from Rust string literals.
11//!
12//! Example
13//! -------
14//!
15//! ```rust
16//! use zombiezen_const_cstr::{const_cstr, ConstCStr};
17//!
18//! use std::os::raw::c_char;
19//! use std::ffi::CStr;
20//!
21//! /// Declare a constant:
22//! const HELLO_CSTR: ConstCStr = const_cstr!("Hello, world!");
23//!
24//! // Imagine this is an `extern "C"` function linked from some other lib.
25//! unsafe fn print_c_string(cstr: *const c_char) {
26//! println!("{}", CStr::from_ptr(cstr).to_str().unwrap());
27//! }
28//!
29//! fn main() {
30//! let goodnight_cstr = const_cstr!("Goodnight, sun!");
31//!
32//! unsafe {
33//! print_c_string(HELLO_CSTR.as_ptr());
34//! print_c_string(goodnight_cstr.as_ptr());
35//! }
36//! }
37//! ```
38//!
39//! Prints:
40//!
41//! ```notest
42//! Hello, world!
43//! Goodnight, sun!
44//! ```
45
46use std::fmt::{self, Display};
47use std::ffi::CStr;
48use std::os::raw::c_char;
49
50/// A reference to a C-compatible string constant.
51#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct ConstCStr {
53 val: &'static str,
54}
55
56impl ConstCStr {
57 /// Unsafely creates a constant C string reference from a string slice.
58 ///
59 /// Prefer using the `const_cstr!` macro than calling this function directly.
60 ///
61 /// # Safety
62 ///
63 /// The provided slice **must** be NUL-terminated and not contain any
64 /// interior NUL bytes.
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// use zombiezen_const_cstr::ConstCStr;
70 ///
71 /// let s = unsafe { ConstCStr::from_str_with_nul_unchecked("foo\0") };
72 /// assert_eq!(s.as_str(), "foo");
73 /// ```
74 #[inline]
75 pub const unsafe fn from_str_with_nul_unchecked(val: &'static str) -> ConstCStr {
76 ConstCStr { val }
77 }
78
79 /// Returns the referenced string without the terminating NUL byte.
80 #[inline]
81 pub fn as_str(self) -> &'static str {
82 &self.val[..self.val.len() - 1]
83 }
84
85 /// Returns the referenced string as a byte slice **without** the
86 /// terminating NUL byte.
87 #[inline]
88 pub fn as_bytes(self) -> &'static [u8] {
89 self.as_str().as_bytes()
90 }
91
92 /// Returns the referenced string as a byte slice, **with** the NUL terminating byte.
93 #[inline]
94 pub const fn as_bytes_with_nul(self) -> &'static [u8] {
95 self.val.as_bytes()
96 }
97
98 /// Returns a pointer to the beginning of the string constant.
99 ///
100 /// Suitable for passing to any function that expects a C-compatible string.
101 /// Since the underlying string is guaranteed to be `'static`,
102 /// the pointer should always be valid.
103 #[inline]
104 pub const fn as_ptr(self) -> *const c_char {
105 self.val.as_bytes().as_ptr() as *const c_char
106 }
107
108 /// Returns `&'static CStr` to the referenced string.
109 #[inline]
110 pub fn as_cstr(self) -> &'static CStr {
111 unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
112 }
113}
114
115impl Display for ConstCStr {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
117 self.as_str().fmt(f)
118 }
119}
120
121impl Default for ConstCStr {
122 /// Returns an empty C string constant.
123 #[inline]
124 fn default() -> ConstCStr {
125 const_cstr!("")
126 }
127}
128
129impl AsRef<str> for ConstCStr {
130 fn as_ref(&self) -> &str {
131 self.as_str()
132 }
133}
134
135impl AsRef<[u8]> for ConstCStr {
136 fn as_ref(&self) -> &[u8] {
137 self.as_bytes()
138 }
139}
140
141impl AsRef<CStr> for ConstCStr {
142 fn as_ref(&self) -> &CStr {
143 self.as_cstr()
144 }
145}
146
147impl From<ConstCStr> for &'static str {
148 fn from(c: ConstCStr) -> &'static str {
149 c.as_str()
150 }
151}
152
153impl From<ConstCStr> for &'static [u8] {
154 fn from(c: ConstCStr) -> &'static [u8] {
155 c.as_bytes()
156 }
157}
158
159impl From<ConstCStr> for *const c_char {
160 fn from(c: ConstCStr) -> *const c_char {
161 c.as_ptr()
162 }
163}
164
165impl From<ConstCStr> for &'static CStr {
166 fn from(c: ConstCStr) -> &'static CStr {
167 c.as_cstr()
168 }
169}
170
171/// Create a C-compatible constant string by appending a NUL byte to the
172/// passed string.
173///
174/// See crate root documentation for example usage.
175///
176/// # Safety
177///
178/// The passed string must not contain any NUL bytes.
179#[macro_export]
180macro_rules! const_cstr {
181 ($strval:expr) => {
182 unsafe { $crate::ConstCStr::from_str_with_nul_unchecked(concat!($strval, "\0")) }
183 };
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test() {
192 const HELLO: ConstCStr = const_cstr!("Hello, World!");
193 assert_eq!(HELLO.as_str(), "Hello, World!");
194 assert_eq!(HELLO.as_bytes(), b"Hello, World!");
195 assert_eq!(HELLO.as_bytes_with_nul(), b"Hello, World!\0");
196 assert_eq!(
197 unsafe { CStr::from_ptr(HELLO.as_ptr()) },
198 CStr::from_bytes_with_nul(b"Hello, World!\0").unwrap(),
199 );
200 assert_eq!(
201 HELLO.as_cstr(),
202 CStr::from_bytes_with_nul(b"Hello, World!\0").unwrap(),
203 );
204 }
205}