Skip to main content

cxx/
cxx_string.rs

1use crate::actually_private::Private;
2use crate::lossy;
3#[cfg(feature = "alloc")]
4use alloc::borrow::Cow;
5#[cfg(feature = "alloc")]
6use alloc::string::String;
7use core::cell::UnsafeCell;
8use core::cmp::Ordering;
9use core::ffi::{CStr, c_char};
10use core::fmt::{self, Debug, Display};
11use core::hash::{Hash, Hasher};
12use core::marker::{PhantomData, PhantomPinned};
13use core::mem::MaybeUninit;
14use core::panic::RefUnwindSafe;
15use core::pin::Pin;
16use core::slice;
17use core::str::{self, Utf8Error};
18
19unsafe extern "C" {
20    #[link_name = "cxxbridge1$cxx_string$init"]
21    fn string_init(this: &mut MaybeUninit<CxxString>, ptr: *const u8, len: usize);
22    #[link_name = "cxxbridge1$cxx_string$destroy"]
23    fn string_destroy(this: &mut MaybeUninit<CxxString>);
24    #[link_name = "cxxbridge1$cxx_string$data"]
25    fn string_data(this: &CxxString) -> *const u8;
26    #[link_name = "cxxbridge1$cxx_string$length"]
27    fn string_length(this: &CxxString) -> usize;
28    #[link_name = "cxxbridge1$cxx_string$clear"]
29    fn string_clear(this: Pin<&mut CxxString>);
30    #[link_name = "cxxbridge1$cxx_string$reserve_total"]
31    fn string_reserve_total(this: Pin<&mut CxxString>, new_cap: usize);
32    #[link_name = "cxxbridge1$cxx_string$push"]
33    fn string_push(this: Pin<&mut CxxString>, ptr: *const u8, len: usize);
34}
35
36/// Binding to C++ `std::string`.
37///
38/// # Invariants
39///
40/// As an invariant of this API and the static analysis of the cxx::bridge
41/// macro, in Rust code we can never obtain a `CxxString` by value. C++'s string
42/// requires a move constructor and may hold internal pointers, which is not
43/// compatible with Rust's move behavior. Instead in Rust code we will only ever
44/// look at a CxxString through a reference or smart pointer, as in `&CxxString`
45/// or `UniquePtr<CxxString>`.
46#[repr(C)]
47pub struct CxxString {
48    #[cfg(not(all(miri, feature = "alloc")))]
49    _private: [u8; 0],
50    #[cfg(all(miri, feature = "alloc"))]
51    _miri: miri::CxxStringRepr,
52    _pinned: PhantomData<PhantomPinned>,
53}
54
55/// Construct a C++ std::string on the Rust stack.
56///
57/// # Syntax
58///
59/// In statement position:
60///
61/// ```
62/// # use cxx::let_cxx_string;
63/// # let expression = "";
64/// let_cxx_string!(var = expression);
65/// ```
66///
67/// The `expression` may have any type that implements `AsRef<[u8]>`. Commonly
68/// it will be a string literal, but for example `&[u8]` and `String` would work
69/// as well.
70///
71/// The macro expands to something resembling `let $var: Pin<&mut CxxString> =
72/// /*???*/;`. The resulting [`Pin`] can be deref'd to `&CxxString` as needed.
73///
74/// # Example
75///
76/// ```
77/// use cxx::{let_cxx_string, CxxString};
78///
79/// fn f(s: &CxxString) {/* ... */}
80///
81/// fn main() {
82///     let_cxx_string!(s = "example");
83///     f(&s);
84/// }
85/// ```
86#[macro_export]
87macro_rules! let_cxx_string {
88    ($var:ident = $value:expr $(,)?) => {
89        let cxx_stack_string = $crate::private::StackString::new();
90        #[allow(unused_mut, unused_unsafe)]
91        let mut $var = match $value {
92            let_cxx_string => unsafe { cxx_stack_string.init(let_cxx_string) },
93        };
94        #[allow(unused_unsafe)]
95        let _cxx_stack_string_drop_guard = unsafe { cxx_stack_string.drop_guard() };
96    };
97}
98
99impl CxxString {
100    /// `CxxString` is not constructible via `new`. Instead, use the
101    /// [`let_cxx_string!`] macro.
102    pub fn new<T: Private>() -> Self {
103        unreachable!()
104    }
105
106    /// Returns the length of the string in bytes.
107    ///
108    /// Matches the behavior of C++ [std::string::size][size].
109    ///
110    /// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
111    pub fn len(&self) -> usize {
112        unsafe { string_length(self) }
113    }
114
115    /// Returns true if `self` has a length of zero bytes.
116    ///
117    /// Matches the behavior of C++ [std::string::empty][empty].
118    ///
119    /// [empty]: https://en.cppreference.com/w/cpp/string/basic_string/empty
120    pub fn is_empty(&self) -> bool {
121        self.len() == 0
122    }
123
124    /// Returns a byte slice of this string's contents.
125    pub fn as_bytes(&self) -> &[u8] {
126        let data = self.as_ptr();
127        let len = self.len();
128        unsafe { slice::from_raw_parts(data, len) }
129    }
130
131    /// Produces a pointer to the first character of the string.
132    ///
133    /// Matches the behavior of C++ [std::string::data][data].
134    ///
135    /// Note that the return type may look like `const char *` but is not a
136    /// `const char *` in the typical C sense, as C++ strings may contain
137    /// internal null bytes. As such, the returned pointer only makes sense as a
138    /// string in combination with the length returned by [`len()`][len].
139    ///
140    /// Modifying the string data through this pointer has undefined behavior.
141    ///
142    /// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
143    /// [len]: #method.len
144    pub fn as_ptr(&self) -> *const u8 {
145        unsafe { string_data(self) }
146    }
147
148    /// Produces a nul-terminated string view of this string's contents.
149    ///
150    /// Matches the behavior of C++ [std::string::c_str][c_str].
151    ///
152    /// If this string contains no internal '\0' bytes, then
153    /// `self.as_c_str().count_bytes() == self.len()`. But if it does, the CStr
154    /// only refers to the part of the string up to the first nul byte.
155    ///
156    /// [c_str]: https://en.cppreference.com/w/cpp/string/basic_string/c_str
157    pub fn as_c_str(&self) -> &CStr {
158        // Since C++11, string[string.size()] is guaranteed to be \0.
159        unsafe { CStr::from_ptr(self.as_ptr().cast::<c_char>()) }
160    }
161
162    /// Validates that the C++ string contains UTF-8 data and produces a view of
163    /// it as a Rust &amp;str, otherwise an error.
164    pub fn to_str(&self) -> Result<&str, Utf8Error> {
165        str::from_utf8(self.as_bytes())
166    }
167
168    /// If the contents of the C++ string are valid UTF-8, this function returns
169    /// a view as a Cow::Borrowed &amp;str. Otherwise replaces any invalid UTF-8
170    /// sequences with the U+FFFD [replacement character] and returns a
171    /// Cow::Owned String.
172    ///
173    /// [replacement character]: char::REPLACEMENT_CHARACTER
174    #[cfg(feature = "alloc")]
175    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
176    pub fn to_string_lossy(&self) -> Cow<str> {
177        String::from_utf8_lossy(self.as_bytes())
178    }
179
180    /// Removes all characters from the string.
181    ///
182    /// Matches the behavior of C++ [std::string::clear][clear].
183    ///
184    /// Note: **unlike** the guarantee of Rust's `std::string::String::clear`,
185    /// the C++ standard does not require that capacity is unchanged by this
186    /// operation. In practice existing implementations do not change the
187    /// capacity but all pointers, references, and iterators into the string
188    /// contents are nevertheless invalidated.
189    ///
190    /// [clear]: https://en.cppreference.com/w/cpp/string/basic_string/clear
191    pub fn clear(self: Pin<&mut Self>) {
192        unsafe { string_clear(self) }
193    }
194
195    /// Ensures that this string's capacity is at least `additional` bytes
196    /// larger than its length.
197    ///
198    /// The capacity may be increased by more than `additional` bytes if the
199    /// implementation chooses, to amortize the cost of frequent reallocations.
200    ///
201    /// **The meaning of the argument is not the same as
202    /// [std::string::reserve][reserve] in C++.** The C++ standard library and
203    /// Rust standard library both have a `reserve` method on strings, but in
204    /// C++ code the argument always refers to total capacity, whereas in Rust
205    /// code it always refers to additional capacity. This API on `CxxString`
206    /// follows the Rust convention, the same way that for the length accessor
207    /// we use the Rust conventional `len()` naming and not C++ `size()` or
208    /// `length()`.
209    ///
210    /// # Panics
211    ///
212    /// Panics if the new capacity overflows usize.
213    ///
214    /// [reserve]: https://en.cppreference.com/w/cpp/string/basic_string/reserve
215    pub fn reserve(self: Pin<&mut Self>, additional: usize) {
216        let new_cap = self
217            .len()
218            .checked_add(additional)
219            .expect("CxxString capacity overflow");
220        unsafe { string_reserve_total(self, new_cap) }
221    }
222
223    /// Appends a given string slice onto the end of this C++ string.
224    pub fn push_str(self: Pin<&mut Self>, s: &str) {
225        self.push_bytes(s.as_bytes());
226    }
227
228    /// Appends arbitrary bytes onto the end of this C++ string.
229    pub fn push_bytes(self: Pin<&mut Self>, bytes: &[u8]) {
230        unsafe { string_push(self, bytes.as_ptr(), bytes.len()) }
231    }
232}
233
234impl Display for CxxString {
235    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236        lossy::display(self.as_bytes(), f)
237    }
238}
239
240impl Debug for CxxString {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242        lossy::debug(self.as_bytes(), f)
243    }
244}
245
246impl PartialEq for CxxString {
247    fn eq(&self, other: &Self) -> bool {
248        self.as_bytes() == other.as_bytes()
249    }
250}
251
252impl PartialEq<CxxString> for str {
253    fn eq(&self, other: &CxxString) -> bool {
254        self.as_bytes() == other.as_bytes()
255    }
256}
257
258impl PartialEq<str> for CxxString {
259    fn eq(&self, other: &str) -> bool {
260        self.as_bytes() == other.as_bytes()
261    }
262}
263
264impl Eq for CxxString {}
265
266impl PartialOrd for CxxString {
267    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
268        Some(self.cmp(other))
269    }
270}
271
272impl Ord for CxxString {
273    fn cmp(&self, other: &Self) -> Ordering {
274        self.as_bytes().cmp(other.as_bytes())
275    }
276}
277
278impl Hash for CxxString {
279    fn hash<H: Hasher>(&self, state: &mut H) {
280        self.as_bytes().hash(state);
281    }
282}
283
284impl fmt::Write for Pin<&mut CxxString> {
285    fn write_str(&mut self, s: &str) -> fmt::Result {
286        self.as_mut().push_str(s);
287        Ok(())
288    }
289}
290
291#[cfg(feature = "std")]
292impl std::io::Write for Pin<&mut CxxString> {
293    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
294        self.as_mut().push_bytes(buf);
295        Ok(buf.len())
296    }
297
298    fn flush(&mut self) -> std::io::Result<()> {
299        Ok(())
300    }
301}
302
303#[doc(hidden)]
304#[repr(C)]
305pub struct StackString {
306    // Static assertions in cxx.cc validate that this is large enough and
307    // aligned enough.
308    space: UnsafeCell<MaybeUninit<[usize; 8]>>,
309}
310
311unsafe impl Sync for StackString {}
312impl RefUnwindSafe for StackString {}
313
314impl StackString {
315    pub fn new() -> Self {
316        StackString {
317            space: UnsafeCell::new(MaybeUninit::uninit()),
318        }
319    }
320
321    #[allow(clippy::mut_from_ref)]
322    pub unsafe fn init(&self, value: impl AsRef<[u8]>) -> Pin<&mut CxxString> {
323        let value = value.as_ref();
324        unsafe {
325            let this = &mut *self.space.get().cast::<MaybeUninit<CxxString>>();
326            string_init(this, value.as_ptr(), value.len());
327            Pin::new_unchecked(&mut *this.as_mut_ptr())
328        }
329    }
330
331    pub unsafe fn drop_guard(&self) -> impl Drop + '_ {
332        struct StackStringDropGuard<'a>(&'a StackString);
333
334        impl<'a> Drop for StackStringDropGuard<'a> {
335            fn drop(&mut self) {
336                unsafe {
337                    let this = &mut *self.0.space.get().cast::<MaybeUninit<CxxString>>();
338                    string_destroy(this);
339                }
340            }
341        }
342
343        StackStringDropGuard(self)
344    }
345}
346
347#[cfg(all(miri, feature = "alloc"))]
348mod miri {
349    use super::CxxString;
350    use alloc::vec::Vec;
351    use core::mem;
352    use core::mem::MaybeUninit;
353    use core::pin::Pin;
354    use core::ptr;
355    use core::slice;
356
357    pub(super) type CxxStringRepr = [MaybeUninit<u8>; mem::size_of::<Vec<u8>>()];
358
359    #[unsafe(export_name = "cxxbridge1$cxx_string$init")]
360    unsafe extern "C" fn string_init(
361        this: &mut MaybeUninit<CxxString>,
362        ptr: *const u8,
363        len: usize,
364    ) {
365        unsafe {
366            this.as_mut_ptr()
367                .cast::<Vec<u8>>()
368                .write(slice::from_raw_parts(ptr, len).to_vec());
369        }
370    }
371
372    #[unsafe(export_name = "cxxbridge1$cxx_string$destroy")]
373    unsafe extern "C" fn string_destroy(this: &mut MaybeUninit<CxxString>) {
374        unsafe {
375            ptr::drop_in_place(this.as_mut_ptr().cast::<Vec<u8>>());
376        }
377    }
378
379    #[unsafe(export_name = "cxxbridge1$cxx_string$data")]
380    unsafe extern "C" fn string_data(this: &CxxString) -> *const u8 {
381        let vec = unsafe { &*ptr::from_ref(this).cast::<Vec<u8>>() };
382        vec.as_ptr()
383    }
384
385    #[unsafe(export_name = "cxxbridge1$cxx_string$length")]
386    unsafe extern "C" fn string_length(this: &CxxString) -> usize {
387        let vec = unsafe { &*ptr::from_ref(this).cast::<Vec<u8>>() };
388        vec.len()
389    }
390
391    #[unsafe(export_name = "cxxbridge1$cxx_string$clear")]
392    unsafe extern "C" fn string_clear(this: Pin<&mut CxxString>) {
393        let vec = unsafe { &mut *ptr::from_mut(this.get_unchecked_mut()).cast::<Vec<u8>>() };
394        vec.clear();
395    }
396
397    #[unsafe(export_name = "cxxbridge1$cxx_string$reserve_total")]
398    unsafe extern "C" fn string_reserve_total(this: Pin<&mut CxxString>, new_cap: usize) {
399        let vec = unsafe { &mut *ptr::from_mut(this.get_unchecked_mut()).cast::<Vec<u8>>() };
400        vec.reserve(new_cap.saturating_sub(vec.len()));
401    }
402
403    #[unsafe(export_name = "cxxbridge1$cxx_string$push")]
404    unsafe extern "C" fn string_push(this: Pin<&mut CxxString>, ptr: *const u8, len: usize) {
405        let vec = unsafe { &mut *ptr::from_mut(this.get_unchecked_mut()).cast::<Vec<u8>>() };
406        vec.extend_from_slice(unsafe { slice::from_raw_parts(ptr, len) });
407    }
408}