const_cstr/
lib.rs

1// Copyright (c) 2015 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//! Create static C-compatible strings from Rust string literals.
10//! 
11//! Example
12//! -------
13//!
14//! ```rust
15//! #[macro_use] extern crate const_cstr;
16//!
17//! use std::os::raw::c_char;
18//! use std::ffi::CStr;
19//!
20//! const_cstr! {
21//!     HELLO_CSTR = "Hello, world!";
22//!
23//!     // Multiple declarations can be made with one invocation.
24//!     // GOODNIGHT_CSTR = "Goodnight, sun!";
25//!
26//!     // But only with the same visibility:
27//!     // pub GOODNIGHT_CSTR = "Goodnight, sun!";
28//!     // ^~~ Error: expected identifier, found `pub` 
29//! }
30//!
31//! // Imagine this is an `extern "C"` function linked from some other lib.
32//! unsafe fn print_c_string(cstr: *const c_char) {
33//!     println!("{}", CStr::from_ptr(cstr).to_str().unwrap());
34//! }
35//!
36//! fn main() {
37//!     // When just passed a literal, returns an rvalue instead.
38//!     let goodnight_cstr = const_cstr!("Goodnight, sun!");
39//!
40//!     unsafe {
41//!         print_c_string(HELLO_CSTR.as_ptr());
42//!         print_c_string(goodnight_cstr.as_ptr());
43//!     }
44//! }
45//! ```
46//!
47//! Prints:
48//!
49//! ```notest
50//! Hello, world!
51//! Goodnight, sun!
52//! ```
53
54use std::os::raw::c_char;
55use std::ffi::CStr;
56
57/// A type representing a static C-compatible string, wrapping `&'static str`.
58///
59/// Note
60/// ----
61/// Prefer the `const_cstr!` macro to create an instance of this struct 
62/// over manual initialization. The macro will include the NUL byte for you.
63#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct ConstCStr {
65    /// The wrapped string value. Not intended to be used for manual initialization.
66    /// Public only to allow initialization by the `const_cstr!` macro.
67    ///
68    /// Includes the NUL terminating byte. Use `to_str()` to get an `&'static str`
69    /// without the NUL terminating byte.
70    pub val: &'static str,
71}
72
73impl ConstCStr {
74    /// Returns the wrapped string, without the NUL terminating byte.
75    ///
76    /// Compare to `CStr::to_str()` which checks that the string is valid UTF-8 first,
77    /// since it starts from an arbitrary pointer instead of a Rust string slice.
78    pub fn to_str(&self) -> &'static str {
79        &self.val[..self.val.len() - 1]
80    }
81
82    /// Returns the wrapped string as a byte slice, **without** the NUL terminating byte.
83    pub fn to_bytes(&self) -> &'static [u8] {
84        self.to_str().as_bytes()
85    }
86
87    /// Returns the wrapped string as a byte slice, *with** the NUL terminating byte.
88    pub fn to_bytes_with_nul(&self) -> &'static [u8] {
89        self.val.as_bytes()
90    }
91
92    /// Returns a pointer to the beginning of the wrapped string.
93    ///
94    /// Suitable for passing to any function that expects a C-compatible string. 
95    /// Since the underlying string is guaranteed to be `'static`, 
96    /// the pointer should always be valid.
97    ///
98    /// Panics
99    /// ------
100    /// If the wrapped string is not NUL-terminated. 
101    /// (Unlikely if you used the `const_cstr!` macro. This is just a sanity check.)
102    pub fn as_ptr(&self) -> *const c_char {
103        let bytes = self.val.as_bytes();
104
105        assert_eq!(bytes[bytes.len() - 1], b'\0');
106
107        self.val.as_bytes().as_ptr() as *const c_char
108    }
109
110    /// Returns the wrapped string as an `&'static CStr`, skipping the length check that
111    /// `CStr::from_ptr()` performs (since we know the length already).
112    ///
113    /// Panics
114    /// ------
115    /// If the wrapped string is not NUL-terminated. 
116    /// (Unlikely if you used the `const_cstr!` macro. This is just a sanity check.)
117    pub fn as_cstr(&self) -> &'static CStr {
118        let bytes = self.val.as_bytes();
119
120        assert_eq!(bytes[bytes.len() - 1], b'\0');
121
122        // This check is safe because of the above assert.
123        // Interior nuls are more of a logic error than a memory saftey issue.
124        unsafe {
125            CStr::from_bytes_with_nul_unchecked(bytes)
126        }
127    }
128}
129
130/// Create a C-compatible string as an rvalue or a `const` binding.
131/// Appends a NUL byte to the passed string.
132///
133/// Multiple `const` declarations can be created with one invocation, but only with the same
134/// visibility (`pub` or not).
135///
136/// See crate root documentation for example usage.
137///
138/// Note
139/// ----
140/// For logical consistency, the passed string(s) should not contain any NUL bytes.
141/// Remember that functions consuming a C-string will only see up to the first NUL byte.
142#[macro_export]
143macro_rules! const_cstr {
144    ($(pub $strname:ident = $strval:expr);+;) => (
145        $(
146            pub const $strname: $crate::ConstCStr = const_cstr!($strval);
147        )+
148    );
149    ($strval:expr) => (
150        $crate::ConstCStr { val: concat!($strval, "\0") }
151    );
152    ($($strname:ident = $strval:expr);+;) => (
153        $(
154            const $strname: $crate::ConstCStr = const_cstr!($strval);
155        )+
156    );
157}
158
159#[test]
160fn test_creates_valid_str() {
161    const_cstr! {
162        HELLO_CSTR = "Hello, world!";
163    }
164
165    let cstr = unsafe { CStr::from_ptr(HELLO_CSTR.as_ptr()) };
166
167    assert_eq!(HELLO_CSTR.to_str(), cstr.to_str().unwrap());
168}
169
170#[cfg(test)]
171mod test_creates_pub_str_mod {
172    const_cstr! {
173        pub FIRST = "first";
174        pub SECOND = "second";
175    }
176}
177
178#[test]
179fn test_creates_pub_str() {
180    assert_eq!(test_creates_pub_str_mod::FIRST.to_str(), "first");
181    assert_eq!(test_creates_pub_str_mod::SECOND.to_str(), "second");
182}