1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
## 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/master/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::<std::string::String>(|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::<std::string::String>(|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` 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 experimental [Luau] JIT backend under the `luau-jit` feature flag. This backend is still under development and not yet ready for production use.
To enable it, just call `lua.enable_jit(true)` before loading Lua code. mlua will automatically trigger JIT compilation for new Lua chunks.
When calling this function with `false` argument, mlua will disable JIT compilation 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")
print(err)
})
.exec()?;
Prints:
```text
Failed to open /nonexistent
No such file or directory (os error 2)
stack traceback:
...
```
[`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<String> = lua.globals().get("ud")?;
ud_mut.push_str(", Rust");
drop(ud_mut);
let ud_ref: UserDataRef<String> = 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<LuaTable> {
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.
|