Skip to main content

luaur_rt/
string.rs

1//! The [`LuaString`] handle. Mirrors `mlua::String`.
2
3use crate::error::{Error, Result};
4use crate::state::{Lua, LuaRef};
5use crate::sync::{NotSync, XRc, NOT_SYNC};
6use crate::sys::*;
7
8/// A garbage-collected Lua string.
9///
10/// Mirrors `mlua::String`. Holds a registry reference to the underlying Lua
11/// string so the bytes stay alive for the handle's lifetime.
12///
13/// Under the `send` feature it is `Send` but never `Sync` — see
14/// [`crate::sync::NotSync`].
15#[derive(Clone)]
16pub struct LuaString {
17    pub(crate) reference: XRc<LuaRef>,
18    pub(crate) _not_sync: NotSync,
19}
20
21impl LuaString {
22    pub(crate) fn from_ref(reference: LuaRef) -> LuaString {
23        LuaString {
24            reference: XRc::new(reference),
25            _not_sync: NOT_SYNC,
26        }
27    }
28
29    /// Push this string onto the owning state's stack.
30    pub(crate) unsafe fn push_to_stack(&self) {
31        self.reference.push();
32    }
33
34    /// Get the raw bytes of the string (a copy).
35    ///
36    /// Mirrors `mlua::String::as_bytes` (we return an owned `Vec<u8>` rather
37    /// than a borrowed guard — a deliberate simplification).
38    pub fn as_bytes(&self) -> Vec<u8> {
39        let state = self.reference.state();
40        unsafe {
41            self.reference.push();
42            let mut len = 0usize;
43            let p = lua_tolstring(state, -1, &mut len);
44            let bytes = if p.is_null() {
45                Vec::new()
46            } else {
47                core::slice::from_raw_parts(p as *const u8, len).to_vec()
48            };
49            lua_pop(state, 1);
50            bytes
51        }
52    }
53
54    /// Get the string as a UTF-8 `&str`, erroring if it is not valid UTF-8.
55    ///
56    /// Mirrors `mlua::String::to_str` (returns an owned `String` here).
57    pub fn to_str(&self) -> Result<String> {
58        let bytes = self.as_bytes();
59        String::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError {
60            from: "string",
61            to: "String".to_string(),
62            message: Some(format!("invalid utf-8: {e}")),
63        })
64    }
65
66    /// Get the string lossily as a Rust `String` (invalid UTF-8 replaced).
67    ///
68    /// Mirrors `mlua::String::to_string_lossy`.
69    pub fn to_string_lossy(&self) -> String {
70        String::from_utf8_lossy(&self.as_bytes()).into_owned()
71    }
72
73    /// The raw bytes with a trailing NUL appended (Lua strings are NUL
74    /// terminated). Mirrors `mlua::String::as_bytes_with_nul`.
75    pub fn as_bytes_with_nul(&self) -> Vec<u8> {
76        let mut v = self.as_bytes();
77        v.push(0);
78        v
79    }
80
81    /// A raw pointer identifying the interned string (for identity
82    /// comparison). Mirrors `mlua::String::to_pointer`.
83    pub fn to_pointer(&self) -> *const std::ffi::c_void {
84        let state = self.reference.state();
85        unsafe {
86            self.reference.push();
87            let p = lua_topointer(state, -1);
88            lua_pop(state, 1);
89            p
90        }
91    }
92
93    /// A `Display`-able view that renders the bytes lossily as UTF-8.
94    /// Mirrors `mlua::String::display`.
95    pub fn display(&self) -> LuaStringDisplay {
96        LuaStringDisplay(self.to_string_lossy())
97    }
98}
99
100/// `Display` adapter returned by [`LuaString::display`].
101pub struct LuaStringDisplay(String);
102
103impl std::fmt::Display for LuaStringDisplay {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.write_str(&self.0)
106    }
107}
108
109impl std::fmt::Debug for LuaString {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        // Mirror mlua: valid utf-8 prints as a normal Rust string literal,
112        // otherwise as a byte-string literal `b"..."`.
113        let bytes = self.as_bytes();
114        match std::str::from_utf8(&bytes) {
115            Ok(s) => write!(f, "{s:?}"),
116            Err(_) => {
117                f.write_str("b\"")?;
118                for &b in &bytes {
119                    match b {
120                        b'\0' => f.write_str("\\0")?,
121                        b'\r' => f.write_str("\\r")?,
122                        b'\n' => f.write_str("\\n")?,
123                        b'\t' => f.write_str("\\t")?,
124                        b'\\' => f.write_str("\\\\")?,
125                        b'"' => f.write_str("\\\"")?,
126                        0x20..=0x7e => f.write_str(&(b as char).to_string())?,
127                        _ => write!(f, "\\x{b:02x}")?,
128                    }
129                }
130                f.write_str("\"")
131            }
132        }
133    }
134}
135
136impl PartialEq for LuaString {
137    fn eq(&self, other: &Self) -> bool {
138        self.as_bytes() == other.as_bytes()
139    }
140}
141
142impl Eq for LuaString {}
143
144impl PartialOrd for LuaString {
145    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
146        Some(self.cmp(other))
147    }
148}
149
150impl Ord for LuaString {
151    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
152        self.as_bytes().cmp(&other.as_bytes())
153    }
154}
155
156impl std::hash::Hash for LuaString {
157    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
158        self.as_bytes().hash(state);
159    }
160}
161
162// --- Comparisons against common Rust byte/str types ------------------------
163
164macro_rules! impl_str_eq {
165    ($($ty:ty => $conv:expr),* $(,)?) => {$(
166        impl PartialEq<$ty> for LuaString {
167            fn eq(&self, other: &$ty) -> bool {
168                let f: fn(&$ty) -> &[u8] = $conv;
169                self.as_bytes() == f(other)
170            }
171        }
172        impl PartialOrd<$ty> for LuaString {
173            fn partial_cmp(&self, other: &$ty) -> Option<std::cmp::Ordering> {
174                let f: fn(&$ty) -> &[u8] = $conv;
175                Some(self.as_bytes().as_slice().cmp(f(other)))
176            }
177        }
178    )*};
179}
180
181impl_str_eq! {
182    str => |s| s.as_bytes(),
183    String => |s| s.as_bytes(),
184    [u8] => |s| s,
185    Vec<u8> => |s| s.as_slice(),
186}
187
188impl PartialEq<&str> for LuaString {
189    fn eq(&self, other: &&str) -> bool {
190        self.as_bytes() == other.as_bytes()
191    }
192}
193impl PartialOrd<&str> for LuaString {
194    fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
195        Some(self.as_bytes().as_slice().cmp(other.as_bytes()))
196    }
197}
198
199impl<const N: usize> PartialEq<&[u8; N]> for LuaString {
200    fn eq(&self, other: &&[u8; N]) -> bool {
201        self.as_bytes() == other.as_slice()
202    }
203}
204impl<const N: usize> PartialOrd<&[u8; N]> for LuaString {
205    fn partial_cmp(&self, other: &&[u8; N]) -> Option<std::cmp::Ordering> {
206        Some(self.as_bytes().as_slice().cmp(other.as_slice()))
207    }
208}
209
210impl PartialEq<std::borrow::Cow<'_, [u8]>> for LuaString {
211    fn eq(&self, other: &std::borrow::Cow<'_, [u8]>) -> bool {
212        self.as_bytes() == other.as_ref()
213    }
214}
215
216/// Helper to create a fresh Lua string from bytes on a given state, returning a
217/// handle. Used by [`Lua::create_string`].
218pub(crate) fn create_string(lua: &Lua, bytes: &[u8]) -> LuaString {
219    let state = lua.state();
220    unsafe {
221        lua_pushlstring(state, bytes.as_ptr() as *const c_char, bytes.len());
222        LuaString::from_ref(lua.pop_ref())
223    }
224}