## 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/khvzak/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::(|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::(|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 = lua.globals().get("ud")?; ud_mut.push_str(", Rust"); drop(ud_mut); let ud_ref: UserDataRef = 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/khvzak/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 { 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 `lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.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.