From 863d8092d611e211c3703e8842cb5fef47024cc3 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Thu, 30 Jan 2025 23:07:02 +0000 Subject: [PATCH] Imporove `BorrowedStr`/`BorrowedBytes` ergonomic. Implement `FromLua` and `IntoLua` for these types to allow working with them directly. --- src/conversion.rs | 92 ++++++++++++++++++++++++++++++++++++++++++- src/string.rs | 96 +++++++++++++++++++++++++++++---------------- tests/conversion.rs | 64 +++++++++++++++++++++++++++++- tests/string.rs | 14 +++++++ 4 files changed, 229 insertions(+), 37 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index c63fd36..2f64719 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,7 +5,7 @@ use std::hash::{BuildHasher, Hash}; use std::os::raw::c_int; use std::path::{Path, PathBuf}; use std::string::String as StdString; -use std::{slice, str}; +use std::{mem, slice, str}; use bstr::{BStr, BString, ByteSlice, ByteVec}; use num_traits::cast; @@ -13,7 +13,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; use crate::state::{Lua, RawLua}; -use crate::string::String; +use crate::string::{BorrowedBytes, BorrowedStr, String}; use crate::table::Table; use crate::thread::Thread; use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; @@ -91,6 +91,94 @@ impl FromLua for String { } } +impl IntoLua for BorrowedStr<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl IntoLua for &BorrowedStr<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.clone().into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl FromLua for BorrowedStr<'_> { + fn from_lua(value: Value, lua: &Lua) -> Result { + let s = String::from_lua(value, lua)?; + let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; + let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } + + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + let s = String::from_stack(idx, lua)?; + let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; + let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } +} + +impl IntoLua for BorrowedBytes<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl IntoLua for &BorrowedBytes<'_> { + #[inline] + fn into_lua(self, _: &Lua) -> Result { + Ok(Value::String(self.borrow.clone().into_owned())) + } + + #[inline] + unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + lua.push_ref(&self.borrow.0); + Ok(()) + } +} + +impl FromLua for BorrowedBytes<'_> { + fn from_lua(value: Value, lua: &Lua) -> Result { + let s = String::from_lua(value, lua)?; + let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); + let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } + + unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { + let s = String::from_stack(idx, lua)?; + let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); + let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; + let borrow = Cow::Owned(s); + Ok(Self { buf, borrow, _lua }) + } +} + impl IntoLua for Table { #[inline] fn into_lua(self, _: &Lua) -> Result { diff --git a/src/string.rs b/src/string.rs index 38fb885..9c86102 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,4 +1,4 @@ -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::os::raw::{c_int, c_void}; @@ -44,13 +44,7 @@ impl String { /// ``` #[inline] pub fn to_str(&self) -> Result { - let BorrowedBytes(bytes, guard) = self.as_bytes(); - let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError { - from: "string", - to: "&str".to_string(), - message: Some(e.to_string()), - })?; - Ok(BorrowedStr(s, guard)) + BorrowedStr::try_from(self) } /// Converts this string to a [`StdString`]. @@ -109,19 +103,21 @@ impl String { /// ``` #[inline] pub fn as_bytes(&self) -> BorrowedBytes { - let (bytes, guard) = unsafe { self.to_slice() }; - BorrowedBytes(&bytes[..bytes.len() - 1], guard) + BorrowedBytes::from(self) } /// Get the bytes that make up this string, including the trailing nul byte. pub fn as_bytes_with_nul(&self) -> BorrowedBytes { - let (bytes, guard) = unsafe { self.to_slice() }; - BorrowedBytes(bytes, guard) + let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); + // Include the trailing nul byte (it's always present but excluded by default) + let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; + BorrowedBytes { buf, borrow, _lua } } + // Does not return the terminating nul byte unsafe fn to_slice(&self) -> (&[u8], Lua) { let lua = self.0.lua.upgrade(); - let slice = unsafe { + let slice = { let rawlua = lua.lock(); let ref_thread = rawlua.ref_thread(); @@ -134,7 +130,7 @@ impl String { // string type let mut size = 0; let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size); - slice::from_raw_parts(data as *const u8, size + 1) + slice::from_raw_parts(data as *const u8, size) }; (slice, lua) } @@ -238,40 +234,45 @@ impl fmt::Display for Display<'_> { } /// A borrowed string (`&str`) that holds a strong reference to the Lua state. -pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua); +pub struct BorrowedStr<'a> { + // `buf` points to a readonly memory managed by Lua + pub(crate) buf: &'a str, + pub(crate) borrow: Cow<'a, String>, + pub(crate) _lua: Lua, +} impl Deref for BorrowedStr<'_> { type Target = str; #[inline(always)] fn deref(&self) -> &str { - self.0 + self.buf } } impl Borrow for BorrowedStr<'_> { #[inline(always)] fn borrow(&self) -> &str { - self.0 + self.buf } } impl AsRef for BorrowedStr<'_> { #[inline(always)] fn as_ref(&self) -> &str { - self.0 + self.buf } } impl fmt::Display for BorrowedStr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } impl fmt::Debug for BorrowedStr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } @@ -280,7 +281,7 @@ where T: AsRef, { fn eq(&self, other: &T) -> bool { - self.0 == other.as_ref() + self.buf == other.as_ref() } } @@ -291,45 +292,65 @@ where T: AsRef, { fn partial_cmp(&self, other: &T) -> Option { - self.0.partial_cmp(other.as_ref()) + self.buf.partial_cmp(other.as_ref()) } } impl Ord for BorrowedStr<'_> { fn cmp(&self, other: &Self) -> cmp::Ordering { - self.0.cmp(other.0) + self.buf.cmp(other.buf) + } +} + +impl<'a> TryFrom<&'a String> for BorrowedStr<'a> { + type Error = Error; + + #[inline] + fn try_from(value: &'a String) -> Result { + let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); + let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError { + from: "string", + to: "&str".to_string(), + message: Some(e.to_string()), + })?; + Ok(Self { buf, borrow, _lua }) } } /// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. -pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua); +pub struct BorrowedBytes<'a> { + // `buf` points to a readonly memory managed by Lua + pub(crate) buf: &'a [u8], + pub(crate) borrow: Cow<'a, String>, + pub(crate) _lua: Lua, +} impl Deref for BorrowedBytes<'_> { type Target = [u8]; #[inline(always)] fn deref(&self) -> &[u8] { - self.0 + self.buf } } impl Borrow<[u8]> for BorrowedBytes<'_> { #[inline(always)] fn borrow(&self) -> &[u8] { - self.0 + self.buf } } impl AsRef<[u8]> for BorrowedBytes<'_> { #[inline(always)] fn as_ref(&self) -> &[u8] { - self.0 + self.buf } } impl fmt::Debug for BorrowedBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + self.buf.fmt(f) } } @@ -338,7 +359,7 @@ where T: AsRef<[u8]>, { fn eq(&self, other: &T) -> bool { - self.0 == other.as_ref() + self.buf == other.as_ref() } } @@ -349,22 +370,31 @@ where T: AsRef<[u8]>, { fn partial_cmp(&self, other: &T) -> Option { - self.0.partial_cmp(other.as_ref()) + self.buf.partial_cmp(other.as_ref()) } } impl Ord for BorrowedBytes<'_> { fn cmp(&self, other: &Self) -> cmp::Ordering { - self.0.cmp(other.0) + self.buf.cmp(other.buf) } } -impl<'a> IntoIterator for BorrowedBytes<'a> { +impl<'a> IntoIterator for &'a BorrowedBytes<'_> { type Item = &'a u8; type IntoIter = slice::Iter<'a, u8>; fn into_iter(self) -> Self::IntoIter { - self.0.iter() + self.iter() + } +} + +impl<'a> From<&'a String> for BorrowedBytes<'a> { + #[inline] + fn from(value: &'a String) -> Self { + let (buf, _lua) = unsafe { value.to_slice() }; + let borrow = Cow::Borrowed(value); + Self { buf, borrow, _lua } } } diff --git a/tests/conversion.rs b/tests/conversion.rs index 3987ba4..e75a3a0 100644 --- a/tests/conversion.rs +++ b/tests/conversion.rs @@ -6,8 +6,8 @@ use std::path::PathBuf; use bstr::BString; use maplit::{btreemap, btreeset, hashmap, hashset}; use mlua::{ - AnyUserData, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef, - Value, + AnyUserData, BorrowedBytes, BorrowedStr, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, + Table, Thread, UserDataRef, Value, }; #[test] @@ -60,6 +60,66 @@ fn test_string_from_lua() -> Result<()> { Ok(()) } +#[test] +fn test_borrowedstr_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world!")?; + let bs = s.to_str()?; + let bs2 = (&bs).into_lua(&lua)?; + assert_eq!(bs2.as_string().unwrap(), "hello, world!"); + + // Push into stack + let table = lua.create_table()?; + table.set("bs", &bs)?; + assert_eq!(bs, table.get::("bs")?); + + Ok(()) +} + +#[test] +fn test_borrowedstr_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, s: BorrowedStr| Ok(s))?; + let s = f.call::("hello, world!")?; + assert_eq!(s, "hello, world!"); + + Ok(()) +} + +#[test] +fn test_borrowedbytes_into_lua() -> Result<()> { + let lua = Lua::new(); + + // Direct conversion + let s = lua.create_string("hello, world!")?; + let bb = s.as_bytes(); + let bb2 = (&bb).into_lua(&lua)?; + assert_eq!(bb2.as_string().unwrap(), "hello, world!"); + + // Push into stack + let table = lua.create_table()?; + table.set("bb", &bb)?; + assert_eq!(bb, table.get::("bb")?.as_bytes()); + + Ok(()) +} + +#[test] +fn test_borrowedbytes_from_lua() -> Result<()> { + let lua = Lua::new(); + + // From stack + let f = lua.create_function(|_, s: BorrowedBytes| Ok(s))?; + let s = f.call::("hello, world!")?; + assert_eq!(s, "hello, world!"); + + Ok(()) +} + #[test] fn test_table_into_lua() -> Result<()> { let lua = Lua::new(); diff --git a/tests/string.rs b/tests/string.rs index 1f849df..f6bdd99 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -143,3 +143,17 @@ fn test_string_wrap() -> Result<()> { Ok(()) } + +#[test] +fn test_bytes_into_iter() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_string("hello")?; + let bytes = s.as_bytes(); + + for (i, &b) in bytes.into_iter().enumerate() { + assert_eq!(b, s.as_bytes()[i]); + } + + Ok(()) +}