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}