mlua_codemp_patch/
string.rs

1use std::borrow::Borrow;
2use std::hash::{Hash, Hasher};
3use std::ops::Deref;
4use std::os::raw::c_void;
5use std::string::String as StdString;
6use std::{cmp, fmt, slice, str};
7
8#[cfg(feature = "serialize")]
9use {
10    serde::ser::{Serialize, Serializer},
11    std::result::Result as StdResult,
12};
13
14use crate::error::{Error, Result};
15use crate::state::Lua;
16use crate::types::ValueRef;
17
18/// Handle to an internal Lua string.
19///
20/// Unlike Rust strings, Lua strings may not be valid UTF-8.
21#[derive(Clone)]
22pub struct String(pub(crate) ValueRef);
23
24impl String {
25    /// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
26    ///
27    /// # Examples
28    ///
29    /// ```
30    /// # use mlua::{Lua, Result, String};
31    /// # fn main() -> Result<()> {
32    /// # let lua = Lua::new();
33    /// let globals = lua.globals();
34    ///
35    /// let version: String = globals.get("_VERSION")?;
36    /// assert!(version.to_str()?.contains("Lua"));
37    ///
38    /// let non_utf8: String = lua.load(r#"  "test\255"  "#).eval()?;
39    /// assert!(non_utf8.to_str().is_err());
40    /// # Ok(())
41    /// # }
42    /// ```
43    #[inline]
44    pub fn to_str(&self) -> Result<BorrowedStr> {
45        let BorrowedBytes(bytes, guard) = self.as_bytes();
46        let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError {
47            from: "string",
48            to: "&str",
49            message: Some(e.to_string()),
50        })?;
51        Ok(BorrowedStr(s, guard))
52    }
53
54    /// Converts this string to a [`StdString`].
55    ///
56    /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
57    ///
58    /// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// # use mlua::{Lua, Result};
64    /// # fn main() -> Result<()> {
65    /// let lua = Lua::new();
66    ///
67    /// let s = lua.create_string(b"test\xff")?;
68    /// assert_eq!(s.to_string_lossy(), "test\u{fffd}");
69    /// # Ok(())
70    /// # }
71    /// ```
72    #[inline]
73    pub fn to_string_lossy(&self) -> StdString {
74        StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
75    }
76
77    /// Get the bytes that make up this string.
78    ///
79    /// The returned slice will not contain the terminating nul byte, but will contain any nul
80    /// bytes embedded into the Lua string.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// # use mlua::{Lua, Result, String};
86    /// # fn main() -> Result<()> {
87    /// # let lua = Lua::new();
88    /// let non_utf8: String = lua.load(r#"  "test\255"  "#).eval()?;
89    /// assert!(non_utf8.to_str().is_err());    // oh no :(
90    /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]);
91    /// # Ok(())
92    /// # }
93    /// ```
94    #[inline]
95    pub fn as_bytes(&self) -> BorrowedBytes {
96        let (bytes, guard) = unsafe { self.to_slice() };
97        BorrowedBytes(&bytes[..bytes.len() - 1], guard)
98    }
99
100    /// Get the bytes that make up this string, including the trailing nul byte.
101    pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
102        let (bytes, guard) = unsafe { self.to_slice() };
103        BorrowedBytes(bytes, guard)
104    }
105
106    unsafe fn to_slice(&self) -> (&[u8], Lua) {
107        let lua = self.0.lua.upgrade();
108        let rawlua = lua.lock();
109        let ref_thread = rawlua.ref_thread();
110        unsafe {
111            mlua_debug_assert!(
112                ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING,
113                "string ref is not string type"
114            );
115
116            let mut size = 0;
117            // This will not trigger a 'm' error, because the reference is guaranteed to be of
118            // string type
119            let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size);
120
121            drop(rawlua);
122            (slice::from_raw_parts(data as *const u8, size + 1), lua)
123        }
124    }
125
126    /// Converts this string to a generic C pointer.
127    ///
128    /// There is no way to convert the pointer back to its original value.
129    ///
130    /// Typically this function is used only for hashing and debug information.
131    #[inline]
132    pub fn to_pointer(&self) -> *const c_void {
133        self.0.to_pointer()
134    }
135}
136
137impl fmt::Debug for String {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        let bytes = self.as_bytes();
140        // Check if the string is valid utf8
141        if let Ok(s) = str::from_utf8(&bytes) {
142            return s.fmt(f);
143        }
144
145        // Format as bytes
146        write!(f, "b\"")?;
147        for &b in bytes {
148            // https://doc.rust-lang.org/reference/tokens.html#byte-escapes
149            match b {
150                b'\n' => write!(f, "\\n")?,
151                b'\r' => write!(f, "\\r")?,
152                b'\t' => write!(f, "\\t")?,
153                b'\\' | b'"' => write!(f, "\\{}", b as char)?,
154                b'\0' => write!(f, "\\0")?,
155                // ASCII printable
156                0x20..=0x7e => write!(f, "{}", b as char)?,
157                _ => write!(f, "\\x{b:02x}")?,
158            }
159        }
160        write!(f, "\"")?;
161
162        Ok(())
163    }
164}
165
166// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that.
167//
168// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
169//
170// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
171// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
172// in other ways.
173impl<T> PartialEq<T> for String
174where
175    T: AsRef<[u8]> + ?Sized,
176{
177    fn eq(&self, other: &T) -> bool {
178        self.as_bytes() == other.as_ref()
179    }
180}
181
182impl PartialEq<String> for String {
183    fn eq(&self, other: &String) -> bool {
184        self.as_bytes() == other.as_bytes()
185    }
186}
187
188impl PartialEq<&String> for String {
189    fn eq(&self, other: &&String) -> bool {
190        self.as_bytes() == other.as_bytes()
191    }
192}
193
194impl Eq for String {}
195
196impl Hash for String {
197    fn hash<H: Hasher>(&self, state: &mut H) {
198        self.as_bytes().hash(state);
199    }
200}
201
202#[cfg(feature = "serialize")]
203impl Serialize for String {
204    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
205    where
206        S: Serializer,
207    {
208        match self.to_str() {
209            Ok(s) => serializer.serialize_str(&s),
210            Err(_) => serializer.serialize_bytes(&self.as_bytes()),
211        }
212    }
213}
214
215/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
216pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua);
217
218impl Deref for BorrowedStr<'_> {
219    type Target = str;
220
221    #[inline(always)]
222    fn deref(&self) -> &str {
223        self.0
224    }
225}
226
227impl Borrow<str> for BorrowedStr<'_> {
228    #[inline(always)]
229    fn borrow(&self) -> &str {
230        self.0
231    }
232}
233
234impl AsRef<str> for BorrowedStr<'_> {
235    #[inline(always)]
236    fn as_ref(&self) -> &str {
237        self.0
238    }
239}
240
241impl fmt::Display for BorrowedStr<'_> {
242    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243        self.0.fmt(f)
244    }
245}
246
247impl fmt::Debug for BorrowedStr<'_> {
248    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249        self.0.fmt(f)
250    }
251}
252
253impl<T> PartialEq<T> for BorrowedStr<'_>
254where
255    T: AsRef<str>,
256{
257    fn eq(&self, other: &T) -> bool {
258        self.0 == other.as_ref()
259    }
260}
261
262impl<T> PartialOrd<T> for BorrowedStr<'_>
263where
264    T: AsRef<str>,
265{
266    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
267        self.0.partial_cmp(other.as_ref())
268    }
269}
270
271/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
272pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua);
273
274impl Deref for BorrowedBytes<'_> {
275    type Target = [u8];
276
277    #[inline(always)]
278    fn deref(&self) -> &[u8] {
279        self.0
280    }
281}
282
283impl Borrow<[u8]> for BorrowedBytes<'_> {
284    #[inline(always)]
285    fn borrow(&self) -> &[u8] {
286        self.0
287    }
288}
289
290impl AsRef<[u8]> for BorrowedBytes<'_> {
291    #[inline(always)]
292    fn as_ref(&self) -> &[u8] {
293        self.0
294    }
295}
296
297impl fmt::Debug for BorrowedBytes<'_> {
298    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299        self.0.fmt(f)
300    }
301}
302
303impl<T> PartialEq<T> for BorrowedBytes<'_>
304where
305    T: AsRef<[u8]>,
306{
307    fn eq(&self, other: &T) -> bool {
308        self.0 == other.as_ref()
309    }
310}
311
312impl<T> PartialOrd<T> for BorrowedBytes<'_>
313where
314    T: AsRef<[u8]>,
315{
316    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
317        self.0.partial_cmp(other.as_ref())
318    }
319}
320
321impl<'a> IntoIterator for BorrowedBytes<'a> {
322    type Item = &'a u8;
323    type IntoIter = slice::Iter<'a, u8>;
324
325    fn into_iter(self) -> Self::IntoIter {
326        self.0.iter()
327    }
328}
329
330#[cfg(test)]
331mod assertions {
332    use super::*;
333
334    #[cfg(not(feature = "send"))]
335    static_assertions::assert_not_impl_any!(String: Send);
336    #[cfg(feature = "send")]
337    static_assertions::assert_impl_all!(String: Send, Sync);
338    #[cfg(feature = "send")]
339    static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync);
340    #[cfg(feature = "send")]
341    static_assertions::assert_impl_all!(BorrowedStr: Send, Sync);
342}