1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright (c) 2015 const-cstr developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! Create static C-compatible strings from Rust string literals.
//! 
//! Example
//! -------
//!
//! ```rust
//! #[macro_use] extern crate const_cstr;
//! // Just for the `libc::c_char` type alias.
//! extern crate libc;
//!     
//! use std::ffi::CStr;
//!
//! const_cstr! {
//!     HELLO_CSTR = "Hello, world!";
//!
//!     // Multiple declarations can be made with one invocation.
//!     // GOODNIGHT_CSTR = "Goodnight, sun!";
//!
//!     // But only with the same visibility:
//!     // pub GOODNIGHT_CSTR = "Goodnight, sun!";
//!     // ^~~ Error: expected identifier, found `pub` 
//! }
//!
//! // Imagine this is an `extern "C"` function linked from some other lib.
//! unsafe fn print_c_string(cstr: *const libc::c_char) {
//!     println!("{}", CStr::from_ptr(cstr).to_str().unwrap());
//! }
//!
//! fn main() {
//!     // When just passed a literal, returns an rvalue instead.
//!     let goodnight_cstr = const_cstr!("Goodnight, sun!");
//!
//!     unsafe {
//!         print_c_string(HELLO_CSTR.as_ptr());
//!         print_c_string(goodnight_cstr.as_ptr());
//!     }
//! }
//! ```
//!
//! Prints:
//!
//! ```notest
//! Hello, world!
//! Goodnight, sun!
//! ```
extern crate libc;

use std::ffi::CStr;

/// A type representing a static C-compatible string, wrapping `&'static str`.
///
/// Note
/// ----
/// Prefer the `const_cstr!` macro to create an instance of this struct 
/// over manual initialization. The macro will include the NUL byte for you.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConstCStr {
    /// The wrapped string value. Not intended to be used for manual initialization.
    /// Public only to allow initialization by the `const_cstr!` macro.
    ///
    /// Includes the NUL terminating byte. Use `to_str()` to get an `&'static str`
    /// without the NUL terminating byte.
    pub val: &'static str,
}

impl ConstCStr {
    /// Returns the wrapped string, without the NUL terminating byte.
    ///
    /// Compare to `CStr::to_str()` which checks that the string is valid UTF-8 first,
    /// since it starts from an arbitrary pointer instead of a Rust string slice.
    pub fn to_str(&self) -> &'static str {
        &self.val[..self.val.len() - 1]
    }

    /// Returns the wrapped string as a byte slice, **without** the NUL terminating byte.
    pub fn to_bytes(&self) -> &'static [u8] {
        self.to_str().as_bytes()
    }

    /// Returns the wrapped string as a byte slice, *with** the NUL terminating byte.
    pub fn to_bytes_with_nul(&self) -> &'static [u8] {
        self.val.as_bytes()
    }

    /// Returns a pointer to the beginning of the wrapped string.
    ///
    /// Suitable for passing to any function that expects a C-compatible string. 
    /// Since the underlying string is guaranteed to be `'static`, 
    /// the pointer should always be valid.
    ///
    /// Panics
    /// ------
    /// If the wrapped string is not NUL-terminated. 
    /// (Unlikely if you used the `const_cstr!` macro. This is just a sanity check.)
    pub fn as_ptr(&self) -> *const libc::c_char {
        let bytes = self.val.as_bytes();

        assert_eq!(bytes[bytes.len() - 1], b'\0');

        self.val.as_bytes().as_ptr() as *const libc::c_char
    }

    /// Returns the wrapped string as an `&'static CStr`, skipping the length check that
    /// `CStr::from_ptr()` performs (since we know the length already).
    ///
    /// Panics
    /// ------
    /// If the wrapped string is not NUL-terminated. 
    /// (Unlikely if you used the `const_cstr!` macro. This is just a sanity check.)
    pub fn as_cstr(&self) -> &'static CStr {
        let bytes = self.val.as_bytes();

        assert_eq!(bytes[bytes.len() - 1], b'\0');

        // This check is safe because of the above assert.
        // Interior nuls are more of a logic error than a memory saftey issue.
        unsafe {
            CStr::from_bytes_with_nul_unchecked(bytes)
        }
    }
}

/// Create a C-compatible string as an rvalue or a `const` binding.
/// Appends a NUL byte to the passed string.
///
/// Multiple `const` declarations can be created with one invocation, but only with the same
/// visibility (`pub` or not).
///
/// See crate root documentation for example usage.
///
/// Note
/// ----
/// For logical consistency, the passed string(s) should not contain any NUL bytes.
/// Remember that functions consuming a C-string will only see up to the first NUL byte.
#[macro_export]
macro_rules! const_cstr {
    ($strval:expr) => (
        $crate::ConstCStr { val: concat!($strval, "\0") }
    );
    ($($strname:ident = $strval:expr);+;) => (
        $(
            const $strname: $crate::ConstCStr = const_cstr!($strval);
        )+
    );
    ($(pub $strname:ident = $strval:expr);+;) => (
        $(
            pub const $strname: $crate::ConstCStr = const_cstr!($strval);
        )+
    );
}

#[test]
fn test_creates_valid_str() {
    const_cstr! {
        HELLO_CSTR = "Hello, world!";
    }

    let cstr = unsafe { CStr::from_ptr(HELLO_CSTR.as_ptr()) };

    assert_eq!(HELLO_CSTR.to_str(), cstr.to_str().unwrap());
}