factorio_mlua/
string.rs

1use std::borrow::{Borrow, Cow};
2use std::hash::{Hash, Hasher};
3use std::string::String as StdString;
4use std::{slice, str};
5
6#[cfg(feature = "serialize")]
7use {
8    serde::ser::{Serialize, Serializer},
9    std::result::Result as StdResult,
10};
11
12use crate::error::{Error, Result};
13use crate::ffi;
14use crate::types::LuaRef;
15use crate::util::{assert_stack, StackGuard};
16
17/// Handle to an internal Lua string.
18///
19/// Unlike Rust strings, Lua strings may not be valid UTF-8.
20#[derive(Clone, Debug)]
21pub struct String<'lua>(pub(crate) LuaRef<'lua>);
22
23impl<'lua> String<'lua> {
24    /// Get a `&str` slice if the Lua string is valid UTF-8.
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// # use mlua::{Lua, Result, String};
30    /// # fn main() -> Result<()> {
31    /// # let lua = Lua::new();
32    /// let globals = lua.globals();
33    ///
34    /// let version: String = globals.get("_VERSION")?;
35    /// assert!(version.to_str()?.contains("Lua"));
36    ///
37    /// let non_utf8: String = lua.load(r#"  "test\255"  "#).eval()?;
38    /// assert!(non_utf8.to_str().is_err());
39    /// # Ok(())
40    /// # }
41    /// ```
42    pub fn to_str(&self) -> Result<&str> {
43        str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
44            from: "string",
45            to: "&str",
46            message: Some(e.to_string()),
47        })
48    }
49
50    /// Converts this string to a [`Cow<str>`].
51    ///
52    /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
53    ///
54    /// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// # use mlua::{Lua, Result};
60    /// # fn main() -> Result<()> {
61    /// let lua = Lua::new();
62    ///
63    /// let s = lua.create_string(b"test\xff")?;
64    /// assert_eq!(s.to_string_lossy(), "test\u{fffd}");
65    /// # Ok(())
66    /// # }
67    /// ```
68    pub fn to_string_lossy(&self) -> Cow<'_, str> {
69        StdString::from_utf8_lossy(self.as_bytes())
70    }
71
72    /// Get the bytes that make up this string.
73    ///
74    /// The returned slice will not contain the terminating nul byte, but will contain any nul
75    /// bytes embedded into the Lua string.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use mlua::{Lua, Result, String};
81    /// # fn main() -> Result<()> {
82    /// # let lua = Lua::new();
83    /// let non_utf8: String = lua.load(r#"  "test\255"  "#).eval()?;
84    /// assert!(non_utf8.to_str().is_err());    // oh no :(
85    /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]);
86    /// # Ok(())
87    /// # }
88    /// ```
89    pub fn as_bytes(&self) -> &[u8] {
90        let nulled = self.as_bytes_with_nul();
91        &nulled[..nulled.len() - 1]
92    }
93
94    /// Get the bytes that make up this string, including the trailing nul byte.
95    pub fn as_bytes_with_nul(&self) -> &[u8] {
96        let lua = self.0.lua;
97        unsafe {
98            let _sg = StackGuard::new(lua.state);
99            assert_stack(lua.state, 1);
100
101            lua.push_ref(&self.0);
102            mlua_debug_assert!(
103                ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING,
104                "string ref is not string type"
105            );
106
107            let mut size = 0;
108            // This will not trigger a 'm' error, because the reference is guaranteed to be of
109            // string type
110            let data = ffi::lua_tolstring(lua.state, -1, &mut size);
111
112            slice::from_raw_parts(data as *const u8, size + 1)
113        }
114    }
115}
116
117impl<'lua> AsRef<[u8]> for String<'lua> {
118    fn as_ref(&self) -> &[u8] {
119        self.as_bytes()
120    }
121}
122
123impl<'lua> Borrow<[u8]> for String<'lua> {
124    fn borrow(&self) -> &[u8] {
125        self.as_bytes()
126    }
127}
128
129// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that.
130//
131// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str`, `String` and `mlua::String`
132// itself.
133//
134// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
135// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
136// in other ways.
137impl<'lua, T> PartialEq<T> for String<'lua>
138where
139    T: AsRef<[u8]>,
140{
141    fn eq(&self, other: &T) -> bool {
142        self.as_bytes() == other.as_ref()
143    }
144}
145
146impl<'lua> Eq for String<'lua> {}
147
148impl<'lua> Hash for String<'lua> {
149    fn hash<H: Hasher>(&self, state: &mut H) {
150        self.as_bytes().hash(state);
151    }
152}
153
154#[cfg(feature = "serialize")]
155impl<'lua> Serialize for String<'lua> {
156    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
157    where
158        S: Serializer,
159    {
160        match self.to_str() {
161            Ok(s) => serializer.serialize_str(s),
162            Err(_) => serializer.serialize_bytes(self.as_bytes()),
163        }
164    }
165}