diff options
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | docs/release_notes/v0.9.md | 360 | ||||
-rw-r--r-- | src/lib.rs | 2 |
4 files changed, 371 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d6967..d773969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v0.9.0-rc.2 + +- Added `#[derive(FromLua)]` macro to opt-in into `FromLua<T> where T: 'static + Clone` (userdata type). +- Support vendored module mode for windows (raw-dylib linking, Rust 1.71+) +- `module` and `vendored` features are now mutually exclusive +- Use `C-unwind` ABI (Rust 1.71+) +- Changed `AsChunk` trait to support capturing wrapped Lua types + ## v0.9.0-rc.1 - `UserDataMethods::add_async_method()` takes `&T` instead of cloning `T` @@ -20,6 +20,8 @@ > **Note** > > Please see the [v0.8](https://github.com/khvzak/mlua/tree/v0.8) branch for the stable versions of `mlua` released to crates.io. +> +> v0.9 release notes can be found [here](docs/release_notes/v0.9.md). `mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide _safe_ (as far as it's possible), high level, easy to use, practical and flexible API. diff --git a/docs/release_notes/v0.9.md b/docs/release_notes/v0.9.md new file mode 100644 index 0000000..66fbc25 --- /dev/null +++ b/docs/release_notes/v0.9.md @@ -0,0 +1,360 @@ +## 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 + +#### 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. + +#### 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 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()` was 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 `-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 +use mlua::FromLua; + +#[derive(Clone, Copy, FromLua)] +struct MyUserData(i32); +``` + +`T` is not required to implement `UserData` because of the new relaxed restrictions on userdata types. @@ -256,7 +256,7 @@ pub use mlua_derive::FromLua; /// /// 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 mode will improve performance of such operations +/// protected mode. Setting this attribute will improve performance of such operations /// with risk of having uncaught exceptions and memory leaks. /// /// ```ignore |