raw_cstr/
lib.rs

1// Copyright (C) 2023 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4//! Raw C String conversion and conversion trait
5//!
6//! If you want constant C strings, use `c"Hello, World"` as
7//! [recently stabilized](https://github.com/rust-lang/rust/pull/117472) instead
8
9#![deny(clippy::unwrap_used)]
10
11use anyhow::{bail, Result};
12use std::{
13    cell::RefCell,
14    collections::HashMap,
15    ffi::{CStr, CString},
16};
17
18struct RawCStrs(RefCell<HashMap<String, *mut i8>>);
19
20impl Drop for RawCStrs {
21    fn drop(&mut self) {
22        self.0.borrow_mut().iter_mut().for_each(|(_, c)| unsafe {
23            #[cfg(target_arch = "aarch64")]
24            drop(CString::from_raw((*c) as *mut u8));
25            #[cfg(not(target_arch = "aarch64"))]
26            drop(CString::from_raw(*c));
27        });
28        self.0.borrow_mut().clear();
29    }
30}
31
32thread_local! {
33    static RAW_CSTRS: RawCStrs = RawCStrs(RefCell::new(HashMap::new()));
34}
35
36/// Create a constant raw C string as a `*mut i8` from a Rust string reference. C Strings are cached,
37/// and creating the same string twice will cost zero additional memory. This is useful when calling
38/// C APIs that take a string as an argument, particularly when the string can't be known at compile
39/// time, although this function is also efficient in space (but not time) when a constant string
40/// is known. For compile-time constants, you can use `c_str!`.
41///
42/// # Safety
43///
44/// - Do *not* use [`String::from_raw_parts`] to convert the pointer back to a [`String`]. This
45///   may cause a double free because the [`String`] will take ownership of the pointer. Use
46///   [`CStr::from_ptr`] instead, and convert to a string with
47///   `.to_str().expect("...").to_owned()` instead.
48///
49pub fn raw_cstr<S>(str: S) -> Result<*mut i8>
50where
51    S: AsRef<str>,
52{
53    RAW_CSTRS.with(|rc| {
54        let mut raw_cstrs_map = rc.0.borrow_mut();
55        let saved = raw_cstrs_map.get(str.as_ref());
56
57        if let Some(saved) = saved {
58            Ok(*saved)
59        } else {
60            #[cfg(target_arch = "aarch64")]
61            let raw = CString::new(str.as_ref())?.into_raw() as *mut i8;
62            #[cfg(not(target_arch = "aarch64"))]
63            let raw = CString::new(str.as_ref())?.into_raw();
64            raw_cstrs_map.insert(str.as_ref().to_string(), raw);
65            Ok(raw)
66        }
67    })
68}
69
70/// A type that can be converted to a raw C string
71pub trait AsRawCstr {
72    /// Get a type as a raw C string
73    fn as_raw_cstr(&self) -> Result<*mut i8>;
74}
75
76impl AsRawCstr for &'static [u8] {
77    /// Get a static slice as a raw C string. Useful for interfaces.
78    fn as_raw_cstr(&self) -> Result<*mut i8> {
79        if self.last().is_some_and(|l| *l == 0) {
80            Ok(self.as_ptr() as *const i8 as *mut i8)
81        } else {
82            bail!("Empty slice or last element is nonzero: {:?}", self);
83        }
84    }
85}
86
87impl AsRawCstr for *mut i8 {
88    fn as_raw_cstr(&self) -> Result<*mut i8> {
89        Ok(*self)
90    }
91}
92
93impl AsRawCstr for &str {
94    fn as_raw_cstr(&self) -> Result<*mut i8> {
95        raw_cstr(self)
96    }
97}
98
99impl AsRawCstr for String {
100    fn as_raw_cstr(&self) -> Result<*mut i8> {
101        raw_cstr(self)
102    }
103}
104
105impl AsRawCstr for CString {
106    fn as_raw_cstr(&self) -> Result<*mut i8> {
107        // Make a copy of the string so that we can return a pointer to it
108        raw_cstr(self.to_str()?)
109    }
110}
111
112impl AsRawCstr for CStr {
113    fn as_raw_cstr(&self) -> Result<*mut i8> {
114        // Make a copy of the string so that we can return a pointer to it
115        raw_cstr(self.to_str()?)
116    }
117}
118
119impl AsRawCstr for &'static CStr {
120    fn as_raw_cstr(&self) -> Result<*mut i8> {
121        // No need to copy for static lifetime CStrs because the pointer
122        // lifetime is also static
123        return Ok(self.as_ptr() as *mut _);
124    }
125}