## mlua v0.10 release notes The v0.10 version of mlua has a 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::("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 is 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. #### `anyhow` feature flag The new `anyhow` feature flag adds `IntoLua` and `Into` 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 could cause a 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::(|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`/`AsRef` 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.