From a1d385c7b7aecee66ca000e72cc7676b4efb8f02 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Wed, 26 Apr 2023 23:17:27 +0100 Subject: [PATCH] Add OwnedString --- src/lib.rs | 4 +++- src/prelude.rs | 4 ++-- src/string.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/string.rs | 19 ++++++++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c81a149..41d31d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,7 +152,9 @@ extern crate mlua_derive; // Unstable features #[cfg(feature = "unstable")] -pub use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData}; +pub use crate::{ + function::OwnedFunction, string::OwnedString, table::OwnedTable, userdata::OwnedAnyUserData, +}; /// Create a type that implements [`AsChunk`] and can capture Rust variables. /// diff --git a/src/prelude.rs b/src/prelude.rs index 32eb440..f5af8e2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -36,9 +36,9 @@ pub use crate::{ SerializeOptions as LuaSerializeOptions, }; -#[cfg(all(feature = "unstable", not(feature = "send")))] +#[cfg(feature = "unstable")] #[doc(no_inline)] pub use crate::{ OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction, - OwnedTable as LuaOwnedTable, + OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable, }; diff --git a/src/string.rs b/src/string.rs index 9ef56c8..9a75cf5 100644 --- a/src/string.rs +++ b/src/string.rs @@ -19,6 +19,27 @@ use crate::types::LuaRef; #[derive(Clone)] pub struct String<'lua>(pub(crate) LuaRef<'lua>); +/// Owned handle to an internal Lua string. +/// +/// The owned handle holds a *strong* reference to the current Lua instance. +/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy* +/// to accidentally cause reference cycles that would prevent destroying Lua instance. +/// +/// [`UserData`]: crate::UserData +#[cfg(feature = "unstable")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))] +#[derive(Clone)] +pub struct OwnedString(pub(crate) crate::types::LuaOwnedRef); + +#[cfg(feature = "unstable")] +impl OwnedString { + /// Get borrowed handle to the underlying Lua string. + #[cfg_attr(feature = "send", allow(unused))] + pub const fn to_ref(&self) -> String { + String(self.0.to_ref()) + } +} + impl<'lua> String<'lua> { /// Get a `&str` slice if the Lua string is valid UTF-8. /// @@ -121,6 +142,14 @@ impl<'lua> String<'lua> { let ref_thread = self.0.lua.ref_thread(); unsafe { ffi::lua_topointer(ref_thread, self.0.index) } } + + /// Convert this handle to owned version. + #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))] + #[inline] + pub fn into_owned(self) -> OwnedString { + OwnedString(self.0.into_owned()) + } } impl<'lua> fmt::Debug for String<'lua> { @@ -202,6 +231,37 @@ impl<'lua> Serialize for String<'lua> { } } +// Additional shortcuts +#[cfg(feature = "unstable")] +impl OwnedString { + /// Get a `&str` slice if the Lua string is valid UTF-8. + /// + /// This is a shortcut for [`String::to_str()`]. + #[inline] + pub fn to_str(&self) -> Result<&str> { + let s = self.to_ref(); + // Reattach lifetime to &self + unsafe { std::mem::transmute(s.to_str()) } + } + + /// Get the bytes that make up this string. + /// + /// This is a shortcut for [`String::as_bytes()`]. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + let s = self.to_ref(); + // Reattach lifetime to &self + unsafe { std::mem::transmute(s.as_bytes()) } + } +} + +#[cfg(feature = "unstable")] +impl fmt::Debug for OwnedString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_ref().fmt(f) + } +} + #[cfg(test)] mod assertions { use super::*; diff --git a/tests/string.rs b/tests/string.rs index 7ca5fdd..f4e9927 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -98,3 +98,22 @@ fn test_string_debug() -> Result<()> { Ok(()) } + +#[cfg(all(feature = "unstable", not(feature = "send")))] +#[test] +fn test_owned_string() -> Result<()> { + let lua = Lua::new(); + + let s = lua.create_string("hello, world!")?.into_owned(); + drop(lua); + + // Shortcuts + assert_eq!(s.as_bytes(), b"hello, world!"); + assert_eq!(s.to_str()?, "hello, world!"); + assert_eq!(format!("{s:?}"), "\"hello, world!\""); + + // Access via reference + assert_eq!(s.to_ref().to_string_lossy(), "hello, world!"); + + Ok(()) +}