blob: 1295e887897f8cd6a13a953c342b783f6ecc5609 [file] [log] [blame]
David Tolnay3384c142020-09-14 00:26:47 -04001use alloc::borrow::Cow;
David Tolnayc5a52f92020-09-14 00:43:29 -04002use alloc::string::String;
David Tolnay3384c142020-09-14 00:26:47 -04003use core::fmt::{self, Debug, Display};
David Tolnay95dab1d2020-11-15 14:32:37 -08004use core::marker::{PhantomData, PhantomPinned};
David Tolnaya7525d32020-11-15 19:13:26 -08005use core::pin::Pin;
David Tolnay3384c142020-09-14 00:26:47 -04006use core::slice;
7use core::str::{self, Utf8Error};
David Tolnay7db73692019-10-20 14:51:12 -04008
9extern "C" {
David Tolnay8f16ae72020-10-08 18:21:13 -070010 #[link_name = "cxxbridge05$cxx_string$data"]
David Tolnay90691f42020-11-14 20:01:46 -080011 fn string_data(this: &CxxString) -> *const u8;
David Tolnay8f16ae72020-10-08 18:21:13 -070012 #[link_name = "cxxbridge05$cxx_string$length"]
David Tolnay90691f42020-11-14 20:01:46 -080013 fn string_length(this: &CxxString) -> usize;
14 #[link_name = "cxxbridge05$cxx_string$push"]
15 fn string_push(this: &mut CxxString, ptr: *const u8, len: usize);
David Tolnay7db73692019-10-20 14:51:12 -040016}
17
18/// Binding to C++ `std::string`.
19///
20/// # Invariants
21///
22/// As an invariant of this API and the static analysis of the cxx::bridge
23/// macro, in Rust code we can never obtain a `CxxString` by value. C++'s string
24/// requires a move constructor and may hold internal pointers, which is not
25/// compatible with Rust's move behavior. Instead in Rust code we will only ever
26/// look at a CxxString through a reference or smart pointer, as in `&CxxString`
27/// or `UniquePtr<CxxString>`.
28#[repr(C)]
29pub struct CxxString {
30 _private: [u8; 0],
David Tolnay95dab1d2020-11-15 14:32:37 -080031 _pinned: PhantomData<PhantomPinned>,
David Tolnay7db73692019-10-20 14:51:12 -040032}
33
34impl CxxString {
35 /// Returns the length of the string in bytes.
36 ///
37 /// Matches the behavior of C++ [std::string::size][size].
38 ///
39 /// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
40 pub fn len(&self) -> usize {
41 unsafe { string_length(self) }
42 }
43
44 /// Returns true if `self` has a length of zero bytes.
David Tolnayd7b8a6e2020-04-24 16:22:55 -070045 ///
46 /// Matches the behavior of C++ [std::string::empty][empty].
47 ///
48 /// [empty]: https://en.cppreference.com/w/cpp/string/basic_string/empty
David Tolnay7db73692019-10-20 14:51:12 -040049 pub fn is_empty(&self) -> bool {
50 self.len() == 0
51 }
52
53 /// Returns a byte slice of this string's contents.
54 pub fn as_bytes(&self) -> &[u8] {
55 let data = self.as_ptr();
56 let len = self.len();
57 unsafe { slice::from_raw_parts(data, len) }
58 }
59
60 /// Produces a pointer to the first character of the string.
61 ///
62 /// Matches the behavior of C++ [std::string::data][data].
63 ///
64 /// Note that the return type may look like `const char *` but is not a
65 /// `const char *` in the typical C sense, as C++ strings may contain
66 /// internal null bytes. As such, the returned pointer only makes sense as a
David Tolnay3cd990f2020-04-24 16:24:26 -070067 /// string in combination with the length returned by [`len()`][len].
David Tolnay7db73692019-10-20 14:51:12 -040068 ///
69 /// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
David Tolnay3cd990f2020-04-24 16:24:26 -070070 /// [len]: #method.len
David Tolnay7db73692019-10-20 14:51:12 -040071 pub fn as_ptr(&self) -> *const u8 {
72 unsafe { string_data(self) }
73 }
74
75 /// Validates that the C++ string contains UTF-8 data and produces a view of
76 /// it as a Rust &amp;str, otherwise an error.
77 pub fn to_str(&self) -> Result<&str, Utf8Error> {
78 str::from_utf8(self.as_bytes())
79 }
80
81 /// If the contents of the C++ string are valid UTF-8, this function returns
82 /// a view as a Cow::Borrowed &amp;str. Otherwise replaces any invalid UTF-8
83 /// sequences with the U+FFFD [replacement character] and returns a
84 /// Cow::Owned String.
85 ///
86 /// [replacement character]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html
87 pub fn to_string_lossy(&self) -> Cow<str> {
88 String::from_utf8_lossy(self.as_bytes())
89 }
David Tolnay90691f42020-11-14 20:01:46 -080090
91 /// Appends a given string slice onto the end of this C++ string.
David Tolnaya7525d32020-11-15 19:13:26 -080092 pub fn push_str(self: Pin<&mut Self>, s: &str) {
David Tolnay95e74b32020-11-14 20:16:22 -080093 self.push_bytes(s.as_bytes());
94 }
95
96 /// Appends arbitrary bytes onto the end of this C++ string.
David Tolnaya7525d32020-11-15 19:13:26 -080097 pub fn push_bytes(self: Pin<&mut Self>, bytes: &[u8]) {
98 unsafe {
99 let this = Pin::into_inner_unchecked(self);
100 string_push(this, bytes.as_ptr(), bytes.len())
101 }
David Tolnay90691f42020-11-14 20:01:46 -0800102 }
David Tolnay7db73692019-10-20 14:51:12 -0400103}
104
105impl Display for CxxString {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
David Tolnayd930a792020-03-25 12:24:40 -0700107 Display::fmt(self.to_string_lossy().as_ref(), f)
David Tolnay7db73692019-10-20 14:51:12 -0400108 }
109}
110
111impl Debug for CxxString {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
David Tolnayd930a792020-03-25 12:24:40 -0700113 Debug::fmt(self.to_string_lossy().as_ref(), f)
David Tolnay7db73692019-10-20 14:51:12 -0400114 }
115}
David Tolnay42ebfa22020-03-25 12:26:22 -0700116
117impl PartialEq for CxxString {
118 fn eq(&self, other: &CxxString) -> bool {
119 self.as_bytes() == other.as_bytes()
120 }
121}
122
123impl PartialEq<CxxString> for str {
124 fn eq(&self, other: &CxxString) -> bool {
125 self.as_bytes() == other.as_bytes()
126 }
127}
128
129impl PartialEq<str> for CxxString {
130 fn eq(&self, other: &str) -> bool {
131 self.as_bytes() == other.as_bytes()
132 }
133}