mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
196 lines
7.4 KiB
Markdown
196 lines
7.4 KiB
Markdown
## mlua v0.10 release notes
|
|
|
|
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
|
|
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
|
|
|
|
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
|
|
|
|
### New features
|
|
|
|
#### `'static` Lua types
|
|
|
|
In previous mlua versions, it was required to have a `'lua` lifetime attached to every Lua value. v0.9 introduced (experimental) owned types that are `'static` without a lifetime attached, but they kept strong references to the Lua instance.
|
|
In v0.10 all Lua types are `'static` and have only weak reference to the Lua instance. It means they are more flexible and can be used in more places without worrying about memory leaks.
|
|
|
|
#### Truly `send` feature
|
|
|
|
In this version Lua is `Send + Sync` when the `send` feature flag is enabled (previously was only `Send`). It means Lua instance and their values can be safely shared between threads and used in multi threaded async contexts.
|
|
|
|
```rust
|
|
let lua = Lua::new();
|
|
|
|
lua.globals().set("i", 0)?;
|
|
let func = lua.load("i = i + ...").into_function()?;
|
|
|
|
std::thread::scope(|s| {
|
|
s.spawn(|| {
|
|
for i in 0..5 {
|
|
func.call::<()>(i).unwrap();
|
|
}
|
|
});
|
|
s.spawn(|| {
|
|
for i in 0..5 {
|
|
func.call::<()>(i).unwrap();
|
|
}
|
|
});
|
|
});
|
|
|
|
assert_eq!(lua.globals().get::<i32>("i")?, 20);
|
|
```
|
|
|
|
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
|
|
|
|
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
|
|
|
|
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
|
|
|
|
#### Register Rust functions with variable number of arguments
|
|
|
|
The new traits `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` have been introduced to provide a way to register Rust functions with variable number of arguments in Lua, without needing to pass all arguments as a tuple.
|
|
|
|
They are used by `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async` methods:
|
|
|
|
```rust
|
|
let add = Function::wrap(|a: i64, b: i64| Ok(a + b));
|
|
|
|
lua.globals().set("add", add).unwrap();
|
|
|
|
// Prints 50
|
|
lua.load(r#"print(add(5, 45))"#).exec().unwrap();
|
|
```
|
|
|
|
To wrap functions that return direct value (non-`Result`) you can use `Function::wrap_raw` method.
|
|
|
|
#### Setting metatable for Lua builtin types
|
|
|
|
For Lua builtin types (like `string`, `function`, `number`, etc.) that have a shared metatable for all instances, it's now possible to set a custom metatable for them.
|
|
|
|
```rust
|
|
let mt = lua.create_table()?;
|
|
mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { "2" } else { "0" }))?)?;
|
|
lua.set_type_metatable::<bool>(Some(mt));
|
|
lua.load("assert(tostring(true) == '2')").exec().unwrap();
|
|
```
|
|
|
|
### Improvements
|
|
|
|
#### New `ObjectLike` trait
|
|
|
|
The `ObjectLike` trait is a combination of the `AnyUserDataExt` and `TableExt` traits used in previous versions. It provides a unified interface for working with Lua tables and userdata.
|
|
|
|
#### `Either<L, R>` enum
|
|
|
|
The `Either<L, R>` enum is a simple enum that can hold either `L` or `R` value. It's useful when you need to return or receive one of two types in a function.
|
|
This type implements `IntoLua` and `FromLua` traits and can generate a meaningful error message when conversion fails.
|
|
|
|
```rust
|
|
let func = Function::wrap(|x: Either<i32, String>| Ok(format!("received: {x}")));
|
|
|
|
lua.globals().set("func", func).unwrap();
|
|
|
|
// Prints: received: 123
|
|
lua.load(r#"print(func(123))"#).exec().unwrap();
|
|
|
|
// Prints: bad argument #1: error converting Lua table to Either<i32, String>
|
|
lua.load(r#"print(pcall(func, {}))"#).exec().unwrap();
|
|
```
|
|
|
|
#### `Lua::exec_raw` helper to execute low-level Lua C API code
|
|
|
|
For advanced users, it's now possible to execute low-level Lua C API code using the `Lua::exec_raw` method.
|
|
|
|
```rust
|
|
let t = lua.create_sequence_from([1, 2, 3, 4, 5])?;
|
|
let sum: i64 = unsafe {
|
|
lua.exec_raw(&t, |state| {
|
|
// top of the stack: table `t`
|
|
let mut sum = 0;
|
|
// push nil as the first key
|
|
mlua::ffi::lua_pushnil(state);
|
|
while mlua::ffi::lua_next(state, -2) != 0 {
|
|
sum += mlua::ffi::lua_tointeger(state, -1);
|
|
// Remove the value, keep the key for the next iteration
|
|
mlua::ffi::lua_pop(state, 1);
|
|
}
|
|
mlua::ffi::lua_pop(state, 1);
|
|
mlua::ffi::lua_pushinteger(state, sum);
|
|
// top of the stack: sum
|
|
})
|
|
}?;
|
|
assert_eq!(sum, 15);
|
|
```
|
|
|
|
The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks.
|
|
|
|
#### `anyhow` feature flag
|
|
|
|
The new `anyhow` feature flag adds `IntoLua` and `Into<mlua::Error>` implementation for the `anyhow::Error` type.
|
|
|
|
```rust
|
|
let f = lua.create_function(|_, ()| {
|
|
Err(anyhow!("error message"))?;
|
|
Ok(())
|
|
})?;
|
|
```
|
|
|
|
### Breaking changes
|
|
|
|
#### Scope changes
|
|
|
|
The following `Scope` methods were changed:
|
|
- Removed `Scope::create_any_userdata`
|
|
- `Scope::create_nonstatic_userdata` is renamed to `Scope::create_userdata`
|
|
|
|
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
|
|
|
|
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
|
|
In mlua v0.9 this can cause read-after-free bug in some edge cases.
|
|
|
|
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
|
|
|
|
```rust
|
|
let data = "hello".to_string();
|
|
lua.scope(|scope| {
|
|
let ud = scope.create_any_userdata_ref(&data)?;
|
|
|
|
// We can only borrow scoped userdata using this method
|
|
ud.borrow_scoped::<String, ()>(|s| {
|
|
assert_eq!(s, "hello");
|
|
})?;
|
|
|
|
Ok(())
|
|
})?;
|
|
```
|
|
|
|
Those methods work for scoped and regular userdata objects (but still require `T: 'static`).
|
|
|
|
#### String changes
|
|
|
|
Since `mlua::String` holds a weak reference to Lua without any guarantees about the lifetime of the underlying data, getting a `&str` or `&[u8]` from it is no longer safe.
|
|
Lua instance can be destroyed while reference to the data is still alive:
|
|
|
|
```rust
|
|
let lua = Lua::new();
|
|
let s: mlua::String = lua.create_string("hello, world")?; // only weak reference to Lua!
|
|
let s_ref: &str = s.to_str()?; // this is not safe!
|
|
drop(lua);
|
|
println!("{s_ref}"); // use after free!
|
|
```
|
|
|
|
To solve this issue, return types of `mlua::String::to_str` and `mlua::String::as_bytes` methods changed to `BorrowedStr` and `BorrowedBytes` respectively.
|
|
|
|
These new types hold a strong reference to the Lua instance and can be safely converted to `&str` or `&[u8]`:
|
|
|
|
```rust
|
|
let lua = Lua::new();
|
|
let s: mlua::String = lua.create_string("hello, world")?;
|
|
let s_ref: mlua::BorrowedStr = s.to_str()?; // The strong reference to Lua is held here
|
|
drop(lua);
|
|
println!("{s_ref}"); // ok
|
|
```
|
|
|
|
The good news is that `BorrowedStr` implements `Deref<Target = str>`/`AsRef<str>` as well as `Display`, `Debug`, `Eq`, `PartialEq` and other traits for easy usage.
|
|
The same applies to `BorrowedBytes`.
|
|
|
|
Unfortunately, `mlua::String::to_string_lossy` cannot return `Cow<'a, str>` anymore, because it requires a strong reference to Lua. It now returns Rust `String` instead.
|