4.7 KiB
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.
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.
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.
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:
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.
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.
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.
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.