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}