mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
362 lines
13 KiB
Markdown
362 lines
13 KiB
Markdown
## mlua v0.9 release notes
|
|
|
|
The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0.
|
|
This document highlights the most important changes. For a full list of changes, see the [CHANGELOG].
|
|
|
|
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
|
|
|
|
### New features
|
|
|
|
#### 1. New Any UserData API
|
|
|
|
This is a long awaited feature that allows to register in Lua foreign types that cannot implement `UserData` trait because of the Rust orphan rules.
|
|
|
|
Now you can register any type that implements [`Any`] trait as a userdata type.
|
|
|
|
Consider the following example:
|
|
|
|
```rust
|
|
lua.register_userdata_type::<std::string::String>(|reg| {
|
|
reg.add_method("len", |_, this, ()| Ok(this.len()));
|
|
|
|
reg.add_method_mut("push", |_, this, s: String| {
|
|
this.push_str(&s);
|
|
Ok(())
|
|
});
|
|
|
|
reg.add_meta_method(MetaMethod::ToString, |lua, this, ()| lua.create_string(this));
|
|
})?;
|
|
|
|
let s = lua.create_any_userdata("hello".to_string())?;
|
|
lua.load(chunk! {
|
|
print("s:len() is " .. $s:len())
|
|
$s:push(" world")
|
|
// Prints: hello, world
|
|
print($s)
|
|
})
|
|
.exec()?;
|
|
```
|
|
|
|
In this example we registered [`std::string::String`] as a userdata type with a set of methods and then created an instance of this type in Lua.
|
|
|
|
It's _not_ required to register a type before using the `Lua::create_any_userdata()` method, instead an empty metatable will be created for you.
|
|
You can also register the same type multiple times with different methods. Any previously created instances will share the old metatable, while new instances will have the new one.
|
|
|
|
The new set of API is called `any_userdata` because it allows to register types that implements [`Any`] trait.
|
|
|
|
[`std::string::String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html
|
|
[`Any`]: https://doc.rust-lang.org/stable/std/any/trait.Any.html
|
|
|
|
#### 2. Scope support for the new any userdata types
|
|
|
|
When you need to create non-static userdata instances in Lua, the usual way is use `Lua::scope()` helper to make them scoped. When out of scope, any scoped objects will be automatically
|
|
dropped. The only downside of this approach is that every new instance will have a new metatable. This is not very fast if you need to create a lot of instances.
|
|
|
|
With the new Any UserData API, you can place non-static references `&T` where `T: 'static` into a scope and they will share a single static metatable.
|
|
|
|
```rust
|
|
lua.register_userdata_type::<std::string::String>(|reg| {
|
|
reg.add_method_mut("replace", |_, this, (pat, to): (String, String)| {
|
|
*this = this.replace(&pat, &to);
|
|
Ok(())
|
|
});
|
|
|
|
reg.add_meta_method(MetaMethod::ToString, |lua, this, ()| lua.create_string(this));
|
|
})?;
|
|
|
|
let mut s = "hello, world".to_string();
|
|
|
|
lua.scope(|scope| {
|
|
// This userdata instance holds only a mutable reference to our string
|
|
let ud = scope.create_any_userdata_ref_mut(&mut s)?;
|
|
lua.load(chunk! {
|
|
$ud:replace("world", "user")
|
|
})
|
|
.exec()
|
|
})?;
|
|
|
|
// Prints: hello, user!
|
|
println!("{s}!");
|
|
```
|
|
|
|
#### 3. Owned types (`unstable`)
|
|
|
|
One of the common questions was how to embed a Lua type into Rust struct to use it later. It was non-trivial to do because of the `'lua` lifetime attached to every Lua value.
|
|
|
|
In v0.9 mlua introduces "owned" types `OwnedTable`/`OwnedFunction`/`OwnedString`/`OwnedAnyUserData`/ `OwnedThread`that are `'static` (no lifetime attached).
|
|
|
|
```rust
|
|
let lua = Lua::new();
|
|
|
|
struct MyStruct {
|
|
table: OwnedTable,
|
|
func: OwnedFunction,
|
|
}
|
|
|
|
let my_struct = MyStruct {
|
|
table: lua.globals().into_owned(),
|
|
func: lua
|
|
.create_function(|_, t: Table| Ok(format!("{t:#?}")))?
|
|
.into_owned(),
|
|
};
|
|
|
|
// It's safe to drop Lua!
|
|
drop(lua);
|
|
|
|
let result = my_struct.func.call::<_, String>(my_struct.table)?;
|
|
println!("{result}");
|
|
```
|
|
|
|
Prior to v0.9, it was possible to do by creating a reference to the Lua value in registry using `Lua::create_registry_value()`
|
|
and retrieving value later using `Lua::registry_value()` method.
|
|
|
|
All owned handles hold a *strong* reference to the current Lua instance.
|
|
Be warned, if you place them 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.
|
|
|
|
Please note this functionality is available under the `unstable` feature flag and not available when the `send` feature is enabled.
|
|
|
|
#### New ffi module
|
|
|
|
In v0.9 release the internal `ffi` module has been moved into the new [`mlua-sys`] crate and became available for public use.
|
|
This crate provides unified Lua FFI API (targeting Lua 5.4) using a (limited) compatibility layer for older versions.
|
|
|
|
mlua re-exports the `ffi` module aliasing the `mlua-sys` crate and provides (unsafe) functionality to work with raw Lua state:
|
|
|
|
```rust
|
|
unsafe {
|
|
unsafe extern "C-unwind" fn lua_add(state: *mut mlua::lua_State) -> i32 {
|
|
let a = mlua::ffi::luaL_checkinteger(state, 1);
|
|
let b = mlua::ffi::luaL_checkinteger(state, 2);
|
|
mlua::ffi::lua_pushinteger(state, a + b);
|
|
1
|
|
}
|
|
|
|
let add = lua.create_c_function(lua_add)?;
|
|
assert_eq!(add.call::<_, i32>((2, 3))?, 5);
|
|
}
|
|
```
|
|
|
|
[`mlua-sys`]: https://crates.io/crates/mlua-sys
|
|
|
|
#### Luau JIT support
|
|
|
|
mlua brings support for the new [Luau] JIT backend under the `luau-jit` feature flag.
|
|
|
|
It will automatically trigger JIT compilation for new Lua chunks. To disable it, just call `lua.enable_jit(false)` before loading Lua code
|
|
(but any previously compiled chunks will remain JIT-compiled).
|
|
|
|
[Luau]: https://luau-lang.org
|
|
|
|
### Improvements
|
|
|
|
#### 1. Better error reporting
|
|
|
|
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
|
|
|
|
In v0.9 it reports a error message with the argument index and expected type:
|
|
|
|
```rust
|
|
let func = lua.create_function(|_, _a: i32| Ok(()))?;
|
|
lua.load(chunk! {
|
|
local ok, err = pcall($func, "not a number")
|
|
// Prints: bad argument #1: error converting Lua string to i32 (expected number or string coercible to number)
|
|
print(err)
|
|
})
|
|
.exec()?;
|
|
```
|
|
|
|
Similar changes have been made for userdata functions and methods:
|
|
|
|
```rust
|
|
lua.register_userdata_type::<&'static str>(|reg| {
|
|
reg.add_method("len", |_, this, ()| Ok(this.len()));
|
|
})?;
|
|
|
|
let s = lua.create_any_userdata("hello")?;
|
|
lua.load(chunk! {
|
|
local ok, err = pcall($s.len, 123)
|
|
// Prints: bad argument `self` to `&str.len`: error converting Lua integer to userdata
|
|
print(err)
|
|
})
|
|
.exec()?;
|
|
```
|
|
|
|
#### 2. Error context
|
|
|
|
Similar to the [`anyhow`] Error type, now it's possible to attach context to Lua errors:
|
|
|
|
```rust
|
|
let read = lua.create_function(|lua, path: String| {
|
|
let bytes = std::fs::read(&path)
|
|
.into_lua_err()
|
|
.context(format!("Failed to open `{path}`"))?;
|
|
Ok(lua.create_string(bytes))
|
|
})?;
|
|
|
|
lua.load(chunk! {
|
|
local ok, err = pcall($read, "/nonexistent")
|
|
/// Prints:
|
|
/// Failed to open /nonexistent
|
|
/// No such file or directory (os error 2)
|
|
/// stack traceback:
|
|
/// ...
|
|
print(err)
|
|
})
|
|
.exec()?;
|
|
```
|
|
|
|
[`anyhow`]: https://crates.io/crates/anyhow
|
|
|
|
#### 4. New methods `Function::wrap`/`AnyUserData::wrap`
|
|
|
|
Sometimes it's useful to have `IntoLua` trait implementation for a Rust function or type `T: Any` without needing to call `Lua::create_function()`/`Lua::create_any_userdata()` methods.
|
|
Since v0.9 you can call the new methods `Function::wrap()`/`AnyUserData::wrap()` that allows to do this. They return an abstract type that `impl IntoLua`:
|
|
|
|
```rust
|
|
lua.globals().set("print_rust", Function::wrap(|_, s: String| Ok(println!("{}", s))))?;
|
|
lua.globals().set("rust_ud", AnyUserData::wrap("hello"))?;
|
|
```
|
|
|
|
In addition there are also `Function::wrap_mut()`/`Function::wrap_async()` methods that allow to wrap mutable and async functions respectively.
|
|
|
|
For a `T: 'UserData + 'static` the `IntoLua` trait is still always implemented.
|
|
|
|
#### `UserDataRef` and `UserDataRefMut` type wrappers
|
|
|
|
The new wrappers `UserDataRef` and `UserDataRefMut` are receivers for userdata type `T` and borrow underlying instance for the lifetime of the wrapper.
|
|
|
|
```rust
|
|
lua.globals()
|
|
.set("ud", AnyUserData::wrap("hello".to_string()))?;
|
|
|
|
let mut ud_mut: UserDataRefMut<String> = lua.globals().get("ud")?;
|
|
ud_mut.push_str(", Rust");
|
|
drop(ud_mut);
|
|
|
|
let ud_ref: UserDataRef<String> = lua.globals().get("ud")?;
|
|
// Prints: hello, Rust
|
|
println!("{}", *ud_ref);
|
|
```
|
|
|
|
In the previous mlua versions the same functionality can be achieved by receiving `AnyUserData` and calling `AnyUserData::borrow()`/`AnyUserData::borrow_mut()` methods.
|
|
|
|
The new wrappers are identical to Rust [`Ref`]/[`RefMut`] types.
|
|
|
|
[`Ref`]: https://doc.rust-lang.org/std/cell/struct.Ref.html
|
|
[`RefMut`]: https://doc.rust-lang.org/std/cell/struct.RefMut.html
|
|
|
|
#### New `AnyUserDataExt` trait
|
|
|
|
Similar to the `TableExt` trait, the `AnyUserDataExt` provides a set of extra methods for the `AnyUserData` type.
|
|
|
|
1) `AnyUserDataExt::get()/set()` to get/set a value by key from the userdata, assuming it has `__index` metamethod.
|
|
|
|
2) `AnyUserDataExt::call()` to call the userdata as a function assuming it has `__call` metamethod.
|
|
|
|
3) `AnyUserData::call_method(name, ...)` to call the userdata method, assuming it has `__index` metamethod and the associated function.
|
|
|
|
#### Pretty formatting Lua values
|
|
|
|
`mlua::Value` implements a new format `:#?` that allows to (recursively) pretty print Lua values:
|
|
|
|
```rust
|
|
println!("{:#?}", lua.globals());
|
|
```
|
|
|
|
Prints:
|
|
```
|
|
{
|
|
["_G"] = table: 0x7fa2d0706260,
|
|
["_VERSION"] = "Lua 5.4",
|
|
["assert"] = function: 0x10451d11d,
|
|
["collectgarbage"] = function: 0x10451d198,
|
|
["coroutine"] = {
|
|
["close"] = function: 0x10451e28f,
|
|
...
|
|
},
|
|
["dofile"] = function: 0x10451d37c,
|
|
...
|
|
}
|
|
```
|
|
|
|
In addition a new method `Value::to_string()` has been added to convert `Value` to a string (using `__tostring` metamethod if available).
|
|
|
|
#### Environment for Lua functions
|
|
|
|
Any Lua functions have an associated environment table that is used to resolve global variables. By default it sets to a Lua globals table.
|
|
|
|
In the new release it's possible to get or update a function environment using `Function::environment()` or `Function::set_environment()` methods respectively.
|
|
|
|
```rust
|
|
let f = lua.load("return a").into_function()?;
|
|
|
|
assert_eq!(f.environment(), Some(lua.globals()));
|
|
|
|
lua.globals().set("a", 1)?;
|
|
assert_eq!(f.call::<_, i32>(())?, 1);
|
|
|
|
f.set_environment(lua.create_table_from([("a", "hello")])?)?;
|
|
assert_eq!(f.call::<_, mlua::String>(())?, "hello");
|
|
```
|
|
|
|
#### Performance optimizations
|
|
|
|
The new mlua version has a number of performance improvements. Please check the [benchmarks results] to see how mlua compares to rlua and rhai.
|
|
|
|
[benchmarks results]: https://github.com/mlua-rs/script-bench-rs
|
|
|
|
### Changes in `module` mode
|
|
|
|
#### New attributes
|
|
|
|
The `lua_module` macro now support the following attributes:
|
|
|
|
- `name=...` - sets name of the module (defaults to the name of the function).
|
|
|
|
Eg.:
|
|
|
|
```rust
|
|
#[mlua::lua_module(name = "alt_module")]
|
|
fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
|
|
lua.create_table()
|
|
}
|
|
```
|
|
|
|
Under the hood a new function `luaopen_alt_module` will be created for the Lua module loader.
|
|
|
|
- `skip_memory_check` - skip memory allocation checks for some operations.
|
|
|
|
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
|
|
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
|
|
|
|
#### Improved Windows target
|
|
|
|
In previous mlua versions, building a Lua module for Windows requires having Lua development libraries installed on the system.
|
|
In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag.
|
|
|
|
With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with
|
|
`lua5x.dll` depending on the enabled Lua version.
|
|
|
|
You still need to have the dll although, linked to application where the module will be loaded.
|
|
|
|
### Breaking changes
|
|
|
|
1) `ToLua`/`ToLuaMulti` traits have been renamed to `IntoLua`/`IntoLuaMulti` respectively (with the methods called `into_lua`/`into_lua_multi`).
|
|
|
|
The main reason for this change is following the Rust self [convention](https://rust-lang.github.io/rust-clippy/master/index.html#/wrong_self_convention).
|
|
|
|
2) Removed `FromLua` implementation for `T: UserData + Clone`.
|
|
|
|
During the usage of mlua, it was found that this implementation is not very useful and prevents custom `FromLua` implementations for `T: UserData`.
|
|
It should be a developer decision to opt-in `FromLua` for their `T` if needed rather than having enabled it unconditionally.
|
|
|
|
To opt-in `FromLua` for `T: Clone` you can use a simple `#[derive(FromLua)]` macro (requires `feature = "macros"`):
|
|
|
|
```rust
|
|
#[derive(Clone, Copy, mlua::FromLua)]
|
|
struct MyUserData(i32);
|
|
```
|
|
|
|
`T` is not required to implement `UserData` because of the new relaxed restrictions on userdata types.
|