Implement `Into<&'a [u8]>` for `BorrowedBytes<'a>`
Currently, to get the underlying &[u8] represented by BorrowedBytes you need to go through Deref, Borrow<[u8]> or AsRef<[u8]>, but they all take a &self rather than self, which causes the returned &[u8] to be tied to a temporary &BorrowedBytes instead of to the lifetime 'a of BorrowedBytes<'a>, which is a reference to &'a mlua::String.
This makes:
fn as_bytes<'a>(s: &'a mlua::String) -> &'a [u8] {
s.as_bytes().as_ref()
}
fail to compile:
cannot return value referencing temporary value
returns a value referencing data owned by the current function rustc (E0515)
By implementing Into<&'a [u8]>:
impl<'a> From<BorrowedBytes<'a>> for &'a [u8] {
fn from(value: BorrowedBytes<'a>) -> Self { value.buf }
}
I can convert BorrowedBytes<'a> into &'a [u8]:
fn as_bytes<'a>(s: &'a mlua::String) -> &'a [u8] {
s.as_bytes().into()
}
Unfortunately it would be unsound. Imagine the situation:
let lua = Lua::new();
let s = lua.create_string("hello, world")?;
let b: &[u8] = s.as_bytes().into();
assert_eq!(b, b"hello, world");
drop(lua);
assert_eq!(b, b"hello, world"); // use after free
Oh right, I forgot that we can drop the entire Lua instance, and BorrowedBytes needs to keep up to hold a strong reference to Lua, but its lifetime is tied only to the mlua::String
I noticed that when I call s.as_bytes() after drop(lua):
let lua = Lua::new();
let s = lua.create_string("hello, world")?;
drop(lua);
assert_eq!(s.as_bytes(), b"hello, world");
the current behavior is to panic with:
panicked at src/string.rs:119:30:
Lua instance is destroyed
In my scenario the Lua instance is not dropped early, so it can be guaranteed to keep available till the app exits.
Do you think it would make sense to introduce the same behavior (panicking when the Lua instance is dropped) for BorrowedBytes::into() as well? Something like this:
impl<'a> From<BorrowedBytes<'a>> for &'a [u8] {
fn from(value: BorrowedBytes<'a>) -> Self {
_ = value._lua.upgrade(); // Ensure the Lua instance is available
value.buf
}
}
and turn the existing _lua from a strong Lua to a weak one (Edit: on second thought, this would affect the existing AsRef<[u8]>, maybe it would be more appropriate as a method on mlua::String that returns a &[u8] instead of a BorrowedBytes - is there any reason we need a BorrowedBytes wrapper?):
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,
+ pub(crate) _lua: WeakLua,
}
It might need a better name though since the semantics of into usually don't imply a panic.