## 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/khvzak/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::("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::(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` enum The `Either` 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| 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 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.