|
| 1 | +## mlua v0.10 release notes |
| 2 | + |
| 3 | +The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees. |
| 4 | +This document highlights the most notable features. For a full list of changes, see the [CHANGELOG]. |
| 5 | + |
| 6 | +[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md |
| 7 | + |
| 8 | +### New features |
| 9 | + |
| 10 | +#### `'static` Lua types |
| 11 | + |
| 12 | +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. |
| 13 | +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. |
| 14 | + |
| 15 | +#### Truly `send` feature |
| 16 | + |
| 17 | +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. |
| 18 | + |
| 19 | +```rust |
| 20 | +let lua = Lua::new(); |
| 21 | + |
| 22 | +lua.globals().set("i", 0)?; |
| 23 | +let func = lua.load("i = i + ...").into_function()?; |
| 24 | + |
| 25 | +std::thread::scope(|s| { |
| 26 | + s.spawn(|| { |
| 27 | + for i in 0..5 { |
| 28 | + func.call::<()>(i).unwrap(); |
| 29 | + } |
| 30 | + }); |
| 31 | + s.spawn(|| { |
| 32 | + for i in 0..5 { |
| 33 | + func.call::<()>(i).unwrap(); |
| 34 | + } |
| 35 | + }); |
| 36 | +}); |
| 37 | + |
| 38 | +assert_eq!(lua.globals().get::<i32>("i")?, 20); |
| 39 | +``` |
| 40 | + |
| 41 | +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. |
| 42 | + |
| 43 | +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. |
| 44 | + |
| 45 | +[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html |
| 46 | + |
| 47 | +#### Register Rust functions with variable number of arguments |
| 48 | + |
| 49 | +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. |
| 50 | + |
| 51 | +They are used by `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async` methods: |
| 52 | + |
| 53 | +```rust |
| 54 | +let add = Function::wrap(|a: i64, b: i64| Ok(a + b)); |
| 55 | + |
| 56 | +lua.globals().set("add", add).unwrap(); |
| 57 | + |
| 58 | +// Prints 50 |
| 59 | +lua.load(r#"print(add(5, 45))"#).exec().unwrap(); |
| 60 | +``` |
| 61 | + |
| 62 | +To wrap functions that return direct value (non-`Result`) you can use `Function::wrap_raw` method. |
| 63 | + |
| 64 | +#### Setting metatable for Lua builtin types |
| 65 | + |
| 66 | +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. |
| 67 | + |
| 68 | +```rust |
| 69 | +let mt = lua.create_table()?; |
| 70 | +mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { "2" } else { "0" }))?)?; |
| 71 | +lua.set_type_metatable::<bool>(Some(mt)); |
| 72 | +lua.load("assert(tostring(true) == '2')").exec().unwrap(); |
| 73 | +``` |
| 74 | + |
| 75 | +### Improvements |
| 76 | + |
| 77 | +#### New `ObjectLike` trait |
| 78 | + |
| 79 | +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. |
| 80 | + |
| 81 | +#### `Either<L, R>` enum |
| 82 | + |
| 83 | +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. |
| 84 | +This type implements `IntoLua` and `FromLua` traits and can generate a meaningful error message when conversion fails. |
| 85 | + |
| 86 | +```rust |
| 87 | +let func = Function::wrap(|x: Either<i32, String>| Ok(format!("received: {x}"))); |
| 88 | + |
| 89 | +lua.globals().set("func", func).unwrap(); |
| 90 | + |
| 91 | +// Prints: received: 123 |
| 92 | +lua.load(r#"print(func(123))"#).exec().unwrap(); |
| 93 | + |
| 94 | +// Prints: bad argument #1: error converting Lua table to Either<i32, String> |
| 95 | +lua.load(r#"print(pcall(func, {}))"#).exec().unwrap(); |
| 96 | +``` |
| 97 | + |
| 98 | +#### `Lua::exec_raw` helper to execute low-level Lua C API code |
| 99 | + |
| 100 | +For advanced users, it's now possible to execute low-level Lua C API code using the `Lua::exec_raw` method. |
| 101 | + |
| 102 | +```rust |
| 103 | +let t = lua.create_sequence_from([1, 2, 3, 4, 5])?; |
| 104 | +let sum: i64 = unsafe { |
| 105 | + lua.exec_raw(&t, |state| { |
| 106 | + // top of the stack: table `t` |
| 107 | + let mut sum = 0; |
| 108 | + // push nil as the first key |
| 109 | + mlua::ffi::lua_pushnil(state); |
| 110 | + while mlua::ffi::lua_next(state, -2) != 0 { |
| 111 | + sum += mlua::ffi::lua_tointeger(state, -1); |
| 112 | + // Remove the value, keep the key for the next iteration |
| 113 | + mlua::ffi::lua_pop(state, 1); |
| 114 | + } |
| 115 | + mlua::ffi::lua_pop(state, 1); |
| 116 | + mlua::ffi::lua_pushinteger(state, sum); |
| 117 | + // top of the stack: sum |
| 118 | + }) |
| 119 | +}?; |
| 120 | +assert_eq!(sum, 15); |
| 121 | +``` |
| 122 | + |
| 123 | +The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks. |
0 commit comments